% ROOT Version 6.40 Release Notes % 2026-5
For more information, see:
The following people have contributed to this new version:
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,\
- The headers in
RooStats/HistFactoryfor data classes related to the measurement definition were merged into theRooStats/HistFactory/Measurement.hheader to simplify usage and development. For now, the whole set of header files is kept for backwards compatibility, but the empty headers will be removed in ROOT 7. - The
TROOT::GetSourceDir()method is deprecated and will be removed in ROOT 6.42. It stopped making sense because the ROOT source is generally not shipped alongside ROOT in thesrc/subdirectory anymore. - Using the
rpathbuild option - deprecated and without effect since ROOT 6.38 - is now scheduled to give configuration errors starting from ROOT 6.42. TDirectory::AddDirectoryStatus()andTDirectory::AddDirectory()have been deprecated. These functions were meant to replace TH1::AddDirectoryStatus(), but never had any effect on ROOT. The associated bit TDirectory::fgAddDirectory was deprecated as well. Although users can set and read the bit, its usage should be stopped completely to avoid any confusion. The bit and functions will be removed in ROOT 7.- The method
RooRealVar::removeRange()and the corresponding method inRooErrorVarhave been deprecated because the name was misleading, and they will be removed in ROOT 6.42. Despite the name, the function did not actually remove a range, but only cleared its limits by setting them to−inf,+infleaving the named range itself defined (sohasRange()would still returntrue). Users should now explicitly callremoveMin()andremoveMax()to remove the lower and upper limits of a range. - The
builtin_zeromqandbuiltin_cppzmqbuild options are deprecated and will be removed in ROOT 6.42. The ZeroMQ library and its C++ bindings are used by the experimental RooFit multiprocessing package, enabled by theroofit_multiprocessbuild option. The ZeroMQ versions it requires (>=4.3.6 or 4.3.5 with the draft API) are now available in the package managers of several platforms, for example Conda, Homebrew, Fedora and the Extra Packages for Enterprise Linux (EPEL). Theroofit_multiprocessfeature is only required by a small set of RooFit power uses, who are using one of these environments and therefore don't require the builtin ZeroMQ library. - The overloads of
RooAbsReal::createChi2()andRooAbsReal::chi2FitTo()that take unbinned RooDataSet data objects are deprecated and will be removed in ROOT 6.42. These methods implemented a specialized chi-square fit for x-y-data with errors in y and optional errors in x, which is conceptually different from the standard histogram-based chi-square in the RooDataHist case and can lead to ambiguous results. To fit 2D data with errors in andxandy, use specialized tools likeTGraphErrors::Fit(), or build an explicit likelihood model if you want to stay with RooFit. - The RooStats::HybridPlot class and the related HybridResult::GetPlot method are deprecated and will be removed in ROOT 6.42. We kindly ask users to write their own ROOT-based plotting code, possibly based on the source code of the deprecated HybridPlot class, as pre-existing plot helpers are usually failing to be flexible enough for large-scale adoption.
- The
TH1Kclass was removed.TMath::KNNDensitycan be used in its stead. - The
TObjectequality operator Pythonization (TObject.__eq__) that was deprecated in ROOT 6.38 and scheduled for removal in ROOT 6.40 is removed. - Comparing C++
nullptrobjects withNonein Python now raises aTypeError, as announced in the ROOT 6.38 release notes. Use truth-value checks likeif not xorx is Noneinstead. - The
TGLIncludes.handTGLWSIncludes.hthat were deprecated in ROOT 6.38 and scheduled for removal are gone now. Please include your required headers like<GL/gl.h>or<GL/glu.h>directly. - The GLEW headers (
GL/eglew.h,GL/glew.h,GL/glxew.h, andGL/wglew.h) that were installed when building ROOT withbuiltin_glew=ONare no longer installed. This is done because ROOT is moving away from GLEW for loading OpenGL extensions. - The
TF1,TF2, andTF3constructors for CINT compatibility were removed. This concerns the templated constructors that additionally took the name of the used functor class and member function. With ROOT 6, these names can be omitted. - The
TMultiGraph::Add(TMultiGraph*, Option_t*)overload that adds the graphs in another TMultiGraph to a TMultiGraph is removed without deprecation. It was inconsistent from a memory ownership standpoint. A TMultiGraph always owns all the added graphs, so adding the same graph instances to two TMultiGraphs forcibly led to double-deletes. If you want to add all graphs fromotherMultiGraphtomultiGraph, please use a for-loop and clone the graphs instead:for (TObject *gr : *otherMultiGraph) { multiGraph->Add(static_cast<TGraph*>(gr->Clone())); }
- The
compression_defaultbuild option was removed. It was supposed to change the default compression algorithm, but didn't actually work with the default parameters ofTFile.
- The general direction of the ROOT project is to become more and more reliant on system packages. It is recommended to make the packages required by ROOT available on the system, e.g. via a package manager, and not with the builtin mechanism. This allows for timely updates and reduces the size of the installed binaries.
- The previously vendored builtins
freetype,xxhash,zlib,lzma,zstd,lz4,libpng,giflib,libjpeg,pcre2andopensslshould be installed in the system if possible. ROOT will not automatically fall-back to their builtin versions if these are not found: the user is informed of that with a helpful message. If installing these dependencies in the system is not possible, the CMake option-Dbuiltin_XYZ=ONhas to be consciously chosen by the user. - For the builtin versions of
freetype,xxhash,zlib,lzma,zstd,lz4,libpng,giflib,libjpeg,pcre2, the source tarballs are now fetched from SPI's website, as for the vast majority of ROOT's builtins, e.g.opensslorxrootd.
- ROOT now adds a RUNPATH to compiled macros. This ensures that when compiled macros are loaded, they load the libraries that belong to the ROOT installation that compiled the macro. See TSystem::SetMakeSharedLib() for customising or disabling the RUNPATH.
rootclingfails if no selection rule is specified and if the creation of a C++ module is not requested.- To ease debugging of unwanted auto-parsing triggered by TClass::GetClass, two new features are introduced:
-
- Give access to the list of classes that triggered auto-parsing:
// Print the list
gInterpreter->Print("autoparsed");
// Get the list/set:
((TCling*)gInterpreter)->GetAutoParseClasses();
-
- Auto-parsing of header files can now be explicitly disabled during the execution of TClass::GetClass;
for example, this can be used to enforce that no header is loaded for I/O operations. To disable the
auto-parsing during
TClass::GetClass, you can either set the shell environment variableROOT_DISABLE_TCLASS_GET_CLASS_AUTOPARSING(to anything) or set therootrckeyRoot.TClass.GetClass.AutoParsingtofalse.
- Auto-parsing of header files can now be explicitly disabled during the execution of TClass::GetClass;
for example, this can be used to enforce that no header is loaded for I/O operations. To disable the
auto-parsing during
- The list of logical volumes gets now rehashed automatically, giving an important performance improvement for setups having a large number of those.
TGeoTessellatednow has efficient, BVH accelerated, navigation function implementations. This makes it possible to useTGeoTessellatedin applications usingTGeoNavigator(such as detector simulation).
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 colorsEGeoColorSet::kFlashy: high-contrast, presentation-friendly colorsEGeoColorSet::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 schemeOverride 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: #21047 for more details
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:
-
Candidate discovery Potentially overlapping volume pairs are identified using oriented bounding-box (OBB) tests, drastically reducing the number of candidates to be examined.
-
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:
TGeoManager::SetNsegments(nseg)(default: 20)TGeoManager::SetNmeshPoints(npoints)(default: 1000)
- 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: #20963 for implementation details and benchmarks
- The behavior or
TDirectoryFile::mkdir(which is alsoTFile::mkdir) was changed regarding the creation of directory hierarchies: callingmkdir("a/b/c", "myTitle")will now assignmyTitleto the innermost directory"c"(before this change it would assign it to"a"). - Fixed a bug in
TDirectoryFile::mkdirwhere passingreturnExistingDirectory = truewould not work properly in case of directory hierarchies. The option is now correctly propagated tomkdir's inner invocations.
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 as0644 - Users with stricter
umaskvalues (e.g.,0077): Files will now be created with more restrictive permissions (e.g.,0600- user-only access) - Users with permissive
umaskvalues (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.
RRawPtrWriteEntryis now part of the stable API, in theROOT::Detailnamespace. It is useful for frameworks passing data to RNTuple asconstraw pointers.
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.
vcveccorebuiltin_vcbuiltin_veccore
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.
- A new RooAbsPdf has been added:
RooStudentT, which describes the location-scale student's t-distribution. - The
RooNumber::setRangeEpsRel()andRooNumber::setRangeEpsAbs()have been introduced 2 years ago in 48637270a9113aa to customize range check behavior to be like before ROOT 6.28, but this has not been proven necessary. Instead, looking up the static variables with the epsilon values incurred significant overhead inRooAbsRealLValue::inRange(), which is visible in many-parameter fits. Therefore, these functions are removed. - The constructors of RooDataSet and RooDataHist that combine datasets via
Index()andImport()now validate that the import names correspond to existing states of the index category. If an imported data slice refers to a category label that is not defined in the index category, the constructor now throws an error. Previously, such labels were silently added as new category states, which could lead to inconsistent datasets when the state names were not synchronized with the model definition. This change prevents the creation of invalid combined datasets and surfaces configuration problems earlier.
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.
RooHistError::getPoissonInterval was reimplemented to use an exact chi-square–based construction (Garwood interval) instead of asymptotic approximations and lookup tables.
Previously:
- For
n > 100, a hard-coded asymptotic formula was used, which was not explained in the documentation. - That approximation corresponds to inverting the Poisson score test and was only correct for
nSigma = 1. - The behavior for
n > 100was not statistically consistent because of the hard transition between exact formula and approximation, resulting in a discrete and unexpected jump in approximation bias. - For small
n, numerical root finding and a lookup table were used.
Now:
- The interval is computed directly using chi-square quantiles for all
n. - The construction provides exact frequentist coverage.
- The implementation works consistently for arbitrary
nSigma. - The hard-coded
n > 100threshold, lookup table, and numerical root finding were removed. - For
n = 0, a one-sided upper limit is used (with lower bound fixed at 0), consistent with the physical constraintmu ≥ 0.
Results may differ from previous ROOT versions for n > 100 or nSigma != 1.
The new implementation is statistically consistent and recommended.
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,
- The message shown in ROOT 6.38 to inform users about change of default compression setting used by Snapshot (was 101 before 6.38, became 505 in 6.38) is now removed.
- Signatures of the HistoND and HistoNSparseD operations have been changed. Previously, the list of input column names was allowed to contain an extra column for events weights. This was done to align the logic with the THnBase::Fill method. But this signature was inconsistent with all other Histo* operations, which have a separate function argument that represents the column to get the weights from. Thus, HistoND and HistoNSparseD both now have a separate function argument for the weights. The previous signature is still supported, but deprecated: a warning will be raised if the user passes the column name of the weights as an extra element of the list of input column names. In a future version of ROOT this functionality will be removed. From now on, creating a (sparse) N-dim histogram with weights should be done by calling
HistoN[Sparse]D(histoModel, inputColumns, weightColumn).
- ROOT dropped support for Python 3.9, meaning ROOT now requires at least Python 3.10.
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.
Because Python no longer automatically relinquishes ownership, some code that previously relied on the old heuristic may now expose:
- Dangling references on the C++ side
- Double deletes
These issues must now be fixed by managing object lifetimes explicitly.
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 referenceWhen 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
-
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. -
Transfer ownership explicitly to C++
- Drop the ownership on the Python side:
ROOT.SetOwnership(obj, False)
- Ensure that the C++ side will take ownership instead, e.g. by explicitly calling
deletein the class destructor or using smart pointers likestd::unique_ptr.
- Drop the ownership on the Python side:
-
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().
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.
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
-
Drop Python ownership explicitly:
ROOT.SetOwnership(obj, False)
-
Pythonize the relevant member function to automatically drop ownership on the Python side (similar to the
TCollectionPythonization described above).
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.
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)TH1.values()now returns a read-only NumPy array by default. Previously it returned a writable array that allowed modifying histogram contents implicitly.- To modify the histogram buffer, you must now explicitly request it:
h.values(writable=True)[0] = 42- When the histogram storage allows it,
TH1.values()returns a zero-copy view. For histogram types that cannot expose their memory layout (TH*CandTProfile*),.values()returns a copy. In these cases passingwritable=Trueis not supported and raises aTypeError.
TH1.values(flow=True)now exposes underflow/ overflow bins when requested.- ROOT histograms now support UHI serialization via intermediate representations with the methods
_to_uhi_and_from_uhi_ - Example usage:
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))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)- Removed stray linebreak when running
root -qwith input files or commands passed with-e. This ensures that there is no superfluous output when runningroot. Note that ROOT 6.38 already removed a spurious newline when startingrootwithout input files or commands. - The ROOT header printed by default when starting the interpreter is now suppressed when scripts or commands are passed on the command line. This makes sure the output doesn't contain unexpected and ROOT version dependent output, which helps in case the output is piped to other commands or compared to reference files.
rootlshas a new flag:-c / --allCycles, which displays all cycles of each object in the inspected file/directory even in normal mode (cycles are already displayed by default with-lor-t).rootcpandrootrmhave a new native implementation, which should make them significantly faster to startup and usable even without Python.
The version of the following packages has been updated:
- cppzeromq: 4.10.0
- fftw3: 3.3.10
- freetype: 2.14.3
- gsl: 2.8
- gtest: 1.17.0
- giflib: 5.2.2
- libjpeg-turbo 3.1.3 is now used as a replacement for libjpeg
- libpng: 1.6.4
- libzeromq: 4.3.5
- lz4: 1.10.0
- lzma: 5.8.2
- xrootd: 5.9.2
- zlib: 1.3.2
- zstd: 1.5.7