ROOT Version 6.40 Release Notes

2026-5

Introduction

For more information, see:

http://root.cern

The following people have contributed to this new version:

Sebastian Alba Vives, Instituto Tecnologico de Costa Rica (TEC),
Bertrand Bellenot, CERN/EP-SFT,
Jakob Blomer, CERN/EP-SFT,
Lukas Breitwieser, CERN/EP-SFT,
Philippe Canal, FNAL,
Olivier Couet, CERN/EP-SFT,
Marta Czurylo, CERN/EP-SFT,
Florine de Geus, CERN/EP-SFT and University of Twente,
Andrei Gheata, CERN/EP-SFT,
Jonas Hahnfeld, CERN/EP-SFT and Goethe University Frankfurt,
Fernando Hueso Gonzalez, IFIC (CSIC-University of Valencia),
Stephan Hageboeck, CERN/EP-SFT,
Aaron Jomy, CERN/EP-SFT,
David Lange, CERN and Princeton,
Sergey Linev, GSI Darmstadt,
Lorenzo Moneta, CERN/EP-SFT,
Christian Ng, https://laserbear.org,
Vincenzo Eduardo Padulano, CERN/EP-SFT,
Giacomo Parolini, CERN/EP-SFT,
Danilo Piparo, CERN/EP-SFT,
Jonas Rembser, CERN/EP-SFT,
Silia Taider, CERN/EP-SFT,
Devajith Valaparambil Sreeramaswamy, CERN/EP-SFT,
Vassil Vassilev, Princeton,
Sandro Wenzel, CERN/EP-ALICE,
Ned Ganchovski, Proektsoft EOOD,

Deprecations

Removals

Build System

Moving from builtin dependencies to system-provided packages

Core Libraries

// Print the list
gInterpreter->Print("autoparsed");
// Get the list/set:
((TCling*)gInterpreter)->GetAutoParseClasses();

Geometry

Extensible color schemes for geometry visualization

ROOT now provides an extensible mechanism to assign colors and transparency to geometry volumes via the new TGeoColorScheme strategy class, used by TGeoManager::DefaultColors().

This improves the readability of geometries imported from formats such as GDML that do not store volume colors. The default behavior now uses a name-based material classification (e.g. metals, polymers, composites, gases) with a Z-binned fallback. Three predefined color sets are provided: * EGeoColorSet::kNatural (default): material-inspired colors * EGeoColorSet::kFlashy: high-contrast, presentation-friendly colors * EGeoColorSet::kHighContrast: darker, saturated colors suited for light backgrounds

Users can customize the behavior at runtime by providing hooks (std::function) to override the computed color, transparency, and/or the Z-based fallback mapping.

Usage examples:

gGeoManager->DefaultColors(); // default (natural) scheme

TGeoColorScheme cs(EGeoColorSet::kFlashy);
gGeoManager->DefaultColors(&cs); // select a predefined scheme

Override examples (hooks):

TGeoColorScheme cs(EGeoColorSet::kNatural);
cs.SetZFallbackHook([](Int_t Z, EGeoColorSet) -> Int_t {
   float g = std::min(1.f, Z / 100.f);
   return TColor::GetColor(g, g, g); // grayscale fallback
});
gGeoManager->DefaultColors(&cs);

A new tutorial macro demonstrates the feature and customization options: tutorials/visualization/geom/geomColors.C.

See: https://github.com/root-project/root/pull/21047 for more details

Accelerated overlap checking with parallel execution

The geometry overlap checker (TGeoChecker::CheckOverlaps) has been significantly refactored and optimized to improve performance and scalability on large detector geometries.

Overlap checking is now structured in three explicit stages:

  1. Candidate discovery Potentially overlapping volume pairs are identified using oriented bounding-box (OBB) tests, drastically reducing the number of candidates to be examined.

  2. Surface point generation and caching Points are generated on the surfaces of the candidate shapes (including additional points on edges and generators) and cached per shape. The sampling density can be tuned via:

  1. Overlap and extrusion checks The actual geometric checks are performed using navigation queries. This stage is now parallelized and automatically uses ROOT’s implicit multithreading when enabled.

Only the final stage is currently parallelized, but it dominates the runtime for complex geometries and shows good strong scaling.

For large assembly-rich detector descriptions such as the ALICE O² geometry, the new candidate filtering reduces the number of overlap candidates by roughly three orders of magnitude compared to the legacy implementation. Combined with multithreaded execution, this reduces the total runtime of a full overlap check from hours to minutes on modern multi-core systems.

Usage example

ROOT::EnableImplicitMT();        // enable parallel checking
gGeoManager->SetNsegments(40);  // increase surface resolution if needed
gGeoManager0->SetNmeshPoints(2000); // increase resolution of points on surface-embedded segments if needed
gGeoManager->CheckOverlaps(1.e-6);

Performance and scaling plots for the CMS Run4 and ALICE aligned geometry are included in the corresponding pull request.

See: https://github.com/root-project/root/pull/20963 for implementation details and benchmarks

I/O

File Permissions Now Respect System umask

ROOT now respects the system umask when creating files, following standard Unix conventions.

Previous behavior: Files were created with hardcoded 0644 permissions (owner read/write, group/others read-only), ignoring the system umask.

New behavior: Files are created with 0666 permissions masked by the system umask (0666 & ~umask), consistent with standard Unix file creation functions like open() and fopen().

Impact: - Users with default umask 022 (most common): No change - files are still created as 0644 - Users with stricter umask values (e.g., 0077): Files will now be created with more restrictive permissions (e.g., 0600 - user-only access) - Users with permissive umask values (e.g., 0002): Files may be created with slightly more open permissions (e.g., 0664 - group-writable)

Note: If you require specific file permissions regardless of umask, you can set umask explicitly before running ROOT (e.g., umask 022) or use chmod after file creation.

This change affects the following classes: TFile, TMapFile, TMemFile, TDCacheFile, TFTP, and TApplicationServer.

TTree

RNTuple

HTTP I/O

Math

Migration from VecCore/Vc to std::experimental::simd for Vectorization

We have migrated the vectorized backends of TMath and TFormula from VecCore/Vc to std::experimental::simd, where available.

On Linux, std::experimental::simd is assumed to be available when ROOT is compiled with C++20 or later, which in practice corresponds to sufficiently recent GCC and Clang compilers. To keep the build system simple and robust, ROOT does not explicitly check compiler versions: users opting into C++20 are expected to use modern toolchains.

Impact on Linux users

ROOT builds with C++17 on Linux no longer provide vectorized TMath and TFormula. This is an intentional and accepted trade-off of the migration. These vectorized features were rarely used, while maintaining them significantly complicated the codebase and build configuration.

Users who rely on vectorized TMath or the vectorized TFormula backend are encouraged to build ROOT with C++20. Doing so restores vectorization through std::experimental::simd, providing a more robust and future-proof implementation.

Windows and Apple silicon users are unaffected

VecCore/Vc did not work on Windows previously, and Vc never provided production-ready support for ARM/Neon, so Apple silicon did not benefit from vectorization before this change.

Build system changes

As part of this migration, the following build options are deprecated. From ROOT 6.42, setting them will result in configuration errors.

Added Modified Anderson-Bjork (ModAB) root-finding algorithm

This is new and efficient bracketing root-finding algorithm. It combines bisection with Anderson-Bjork’s method to achieve superlinear convergence (e.i. = 1.7-1.8), while preserving worst-case optimality. According to the benchmarks, it outperforms classical algorithms like ITP, Brent and Ridders. It is implemented as the ROOT::Math::ModABRootFinder class.

RooFit

General changes

ONNX model integration via RooONNXFunction

A new class RooONNXFunction has been introduced to enable the use of machine learning models in ONNX format directly within RooFit workflows.

RooONNXFunction wraps an ONNX model as a RooAbsReal, allowing it to be used as a building block in likelihoods, fits, and statistical analyses without additional boilerplate code. The class supports models with one or more statically-shaped input tensors and a single scalar output. The class was designed to share workspaces with neural functions for combined fits in RooFit-based frameworks written in C++. Therefore, the RooONNXFunction doesn’t depend on any Python packages and fully supports ROOT IO,

Key features:

Deprecation of the the constant term optimization for legacy test statistic classes

The RooFit::Optimize() option (constant term optimization) has been deprecated and will be removed in ROOT 6.42. This option only affects the legacy evaluation backend.

Important behavior change: Constant term optimization is now disabled by default when using the legacy backend. Previously, it was enabled by default. As a result, users who still rely on the legacy backend may observe slower fits.

The default vectorized CPU evaluation backend (introduced in ROOT 6.32) already performs these optimizations automatically and is not affected by this change. Users are strongly encouraged to switch to the vectorized CPU backend if they are still using the legacy backend.

If the vectorized backend does not work for a given use case, please report it by opening an issue on the ROOT GitHub repository.

New implementation of RooHistError::getPoissonInterval

RooHistError::getPoissonInterval was reimplemented to use an exact chi-square–based construction (Garwood interval) instead of asymptotic approximations and lookup tables.

Previously:

Now:

Results may differ from previous ROOT versions for n > 100 or nSigma != 1. The new implementation is statistically consistent and recommended.

Removal of global expensive object caching in RooFit

The global “expensive object cache” used in RooFit to store numeric integrals and intermediate histogram results has been removed.

While originally intended as a performance optimization, this mechanism could lead to incorrect results due to cache collisions: cached integrals or histograms created in one context (e.g. during plotting) could be reused unintentionally in a different context, even when the underlying configuration had changed.

Given the risk of silently incorrect physics results, and the absence of known workflows that depend critically on this feature, this global caching mechanism has been removed. If you encounter performance regressions in workflows involving expensive integrals or convolutions, we encourage you to report your use case and performance regression as a GitHub issue, so that targeted and robust optimizations can be developed,

RDataFrame

Histograms

A first version of the new histogram package is now available in the ROOT::Experimental namespace. The main user-facing classes are ROOT::Experimental::RHist and ROOT::Experimental::RHistEngine, as well as the three supported axis types: ROOT::Experimental::RRegularAxis, ROOT::Experimental::RVariableBinAxis, ROOT::Experimental::RCategoricalAxis. The two histogram classes are templated on the bin content type and can be multidimensional with dynamic axis types at run-time. Histograms can be filled via the known Fill method, with arguments passed to the variadic function template or as std::tuple. An optional weight must be wrapped in ROOT::Experimental::RWeight and passed as the last argument. Examples are available as tutorials in the documentation.

A key feature of the new histogram package is concurrent filling using atomic instructions. Support is built into the design of the classes and requires no change of the bin content type: For RHistEngine, directly call one of the FillAtomic methods, taking the same arguments as Fill. For RHist, first construct an ROOT::Experimental::RHistConcurrentFiller and then create RHistFillContexts, which will efficiently handle global histogram statistics.

Experimental support for the new histograms is integrated into RDataFrame: The single overloaded method Hist() allows to fill one- and multidimensional histograms.

auto hist1 = df.Hist(10, {5.0, 15.0}, "x");
auto hist2 = df.Hist({axis1, axis2}, {"x", "y"}, "w");

For the moment, histograms can be merged, scaled, sliced, rebinned, and projected. Furthermore, one-dimensional histograms can be converted to the known TH1 via free-standing functions provided in the ROOT::Experimental::Hist namespace. More operations and conversion for higher dimensions will be added in the future.

Note that all classes are in the ROOT::Experimental namespace and remain under active development. As such, interfaces may change at any moment, without consideration of compatibility. Nevertheless, we encourage interested parties to test the provided functionality and solicit feedback. This will allow improving the interfaces and their documentation, and prioritize among missing features. Once the class layout is finalized, we will also support serialization (“streaming”) to ROOT files, which is currently disabled.

Python Interface

Change in memory ownership heuristics

In previous ROOT versions, if a C++ member function took a non-const raw pointer, e.g.

MyClass::add(T* obj)

then calling this method from Python on an instance

my_instance.add(obj)

would assume that ownership of obj is transferred to my_instance.

In practice, many such functions do not actually take ownership. As a result, this heuristic caused several memory leaks.

Starting with ROOT 6.40, the ROOT Python interfaces no longer assumes ownership transfer for non-const raw pointer arguments.

What does this mean for you?

Because Python no longer automatically relinquishes ownership, some code that previously relied on the old heuristic may now expose:

These issues must now be fixed by managing object lifetimes explicitly.

Dangling references

A dangling reference occurs when C++ holds a pointer or reference to an object that has already been deleted on the Python side.

Example

obj = ROOT.MyClass()
my_instance.foo(obj)
del obj  # C++ may still hold a pointer: dangling reference

When the Python object is deleted, the memory associated with the C++ object is also freed. But the C++ side may want to delete the object as well, for instance if the destructor of the class of my_instance also calls delete obj.

Possible remedies

  1. Keep the Python object alive

    Assign the object to a Python variable that lives at least as long as the C++ reference.

    Example: Python lifeline

    obj = ROOT.MyClass()
    my_instance.foo(obj)
    # Setting `obj` as an attribute of `my_instance` makes sure that it's not
    # garbage collected before `my_instance` is deleted:
    my_instance._owned_obj = obj
    del obj  # Reference counter for `obj` doesn't hit zero here

    Setting the lifeline reference could also be done in a user-side Pythonization of MyClass::foo.

  2. Transfer ownership explicitly to C++

  3. Rely on Pythonizations that imply ownership transfer

    If the object is stored in a non-owning collection such as a default-constructed TCollection (e.g. TList), you can make the collection owning before adding any elements:

    coll.SetOwner(True)

    This will imply ownership transfer to the C++ side when adding elements with TCollection::Add().

Note on TCollection Pythonization

TCollection-derived classes are Pythonized such that when an object is added to an owning collection via TCollection::Add(), Python ownership is automatically dropped.

If you identify other cases where such a Pythonization would be beneficial, please report them via a GitHub issue. Users can also implement custom Pythonizations outside ROOT if needed.

Double deletes

A double delete indicates that C++ already owns the object, but Python still attempts to delete it.

In this case, you do not need to ensure C++ ownership, as it already exists. Instead, ensure that Python does not delete the object.

Possible remedies

  1. Drop Python ownership explicitly: Python ROOT.SetOwnership(obj, False)

  2. Pythonize the relevant member function to automatically drop ownership on the Python side (similar to the TCollection Pythonization described above).

Temporary compatibility option

You can temporarily restore the old heuristic by calling:

ROOT.SetHeuristicMemoryPolicy(True)

after importing ROOT.

This option is intended for debugging only and will be removed in ROOT 6.44.

Drop support for calling C++ functions with non-const pointer references

From now on, we disallow calling C++ functions with non-const pointer references (T*&) from Python. These allow pointer rebinding, which cannot be represented safely in Python and could previously lead to confusing behavior. A TypeError is now raised. Typical ROOT usage is unaffected.

In the rare case where you want to call such a function, please change the C++ interface or - if the interface is outside your control - write a wrapper function that avoids non-const pointer references as arguments.

For example, a function with the signature bool setPtr(MyClass *&) could be wrapped by a function that augments the return value with the updated pointer:

ROOT.gInterpreter.Declare("""
    std::pair<bool, MyClass*> setPtrWrapper(MyClass *ptr) {
       bool out = setPtr(ptr);
       return {out, ptr};
    }
""")

Then, call it from Python as follows:

# Use tuple unpacking for convenience
_, ptr = cppyy.gbl.setPtrWrapper(ptr)

UHI

Backwards incompatible changes

h.values(writable=True)[0] = 42

New features

h = ROOT.TH1D("h", "h", 10, -5, 5)
h[...] = np.arange(10)

json_str = json.dumps(h, default=uhi.io.json.default)

h_from_uhi = ROOT.TH1D(json.loads(json_str, , object_hook=uhi.io.json.object_hook))

Removed TCollection.count() Pythonization

The Python-only TCollection.count() method has been removed. The meaning of the underlying comparison was ambiguous for C++ objects: depending on the element class, it might have counted by matching by value equality or pointer equality. This behavior can vary silently between classes and lead to inconsistent or misleading results, so it was safer to remove the count() method.

Users who need to count occurrences in a TCollection can explicitly implement the desired comparison logic in Python, for example:

sum(1 for x in collection if x is obj)   # pointer comparison
sum(1 for x in collection if x == obj)   # value comparison (if defined for the element C++ class)

Machine Learning integration

ROOT provides built-in support for feeding data from ROOT files directly into ML training workflows, without intermediate conversions to other formats. The central tool for this, previously known as RBatchGenerator in the TMVA namespace, has been renamed to ROOT.Experimental.ML.RDataLoader and redesigned in this release. Note that the interface and functionality is still experimental: don’t rely on it for production purposes, but feedback is much appreciated! Tutorials are updated to demonstrate the new interface.

New RDataLoader interface

RDataLoader is now a Python class that exposes methods for splitting the dataset into training and test sets, and reading batches in 3 formats: NumPy arrays, PyTorch tensors and TensorFlow datasets. If more formats became popular, they could be easily added in the future.

Usage:

dl = ROOT.Experimental.ML.RDataLoader(
    df,
    batch_size=1024,
    batches_in_memory=10,
    target="label",
)

train, test = dl.train_test_split(test_size=0.2)

for x, y in train.as_torch(): ...
for x, y in test.as_numpy(): ...
for x, y in train.as_tensorflow(): ...

A new parameter batches_in_memory controls how many batches worth of data are held in the in-memory shuffle buffer at once. Larger values improve shuffling quality at the cost of higher memory usage.

Cluster-aligned reading and improved shuffling

The loader now reads data in chunks aligned to TTree/RNTuple cluster boundaries on disk rather than fixed-size blocks into an in-memory buffer. Entries within the buffer are shuffled together before batching if shuffling is enabled. This is done to ensure most efficient I/O scheduling.

Multiple RDataFrames as input

RDataLoader now accepts a list of RDataFrames as input allowing data from multiple files or sources to be combined transparently into a single training stream.

ROOT executable

Command-line utilities

JavaScript ROOT

Experimental features

Opting out of object auto registration

In preparation for ROOT 7, ROOT 6.40 introduces an experimental mode for opting out of the auto-registration of objects. In ROOT 7, this will be the default, and an opt-in will be required to make these objects auto-register themselves. The table below shows which objects currently honour this mode, and which objects are planned to be added on the path to ROOT 7.

The planned ROOT 7 behaviour can be enabled in one of three ways: 1. ROOT::Experimental::DisableObjectAutoRegistration(): This disables auto registration for the current thread. 2. Setting the environment variable ROOT_OBJECT_AUTO_REGISTRATION=0: This sets the default for every thread that starts. 3. In .rootrc, set the entry Root.ObjectAutoRegistration: 0: This sets the default for every thread that starts.

Note that method 1 affects only the current thread, whereas methods 2 and 3 set the default for every thread that is started in this ROOT session. Using ROOT::Experimental::EnableObjectAutoRegistration(), the auto-registration can be enabled for a single thread without affecting the rest of the session.

Consult the doxygen documentation of these functions in the ROOT::Experimental namespace for details.

Honours DisableObjectAutoRegistration()? Could this be disabled previously?
TH1 and derived Yes TH1::AddDirectoryStatus()
TGraph2D Yes TH1::AddDirectoryStatus()
RooPlot Yes RooPlot::addDirectoryStatus()
TEfficiency Yes No
TProfile2D Yes TH1::AddDirectoryStatus()
TEntryList No, planned for 6.42 No
TEventList No, planned for 6.42 No
TFunction No, work in progress No

Versions of built-in packages

The version of the following packages has been updated:

Items addressed for this release

More than 130 items were addressed for this release: