diff --git a/GeoModelCore/GeoModelHelpers/GeoModelHelpers/printVolume.h b/GeoModelCore/GeoModelHelpers/GeoModelHelpers/printVolume.h
new file mode 100644
index 0000000000000000000000000000000000000000..bb3f7e8067e6dc391263eb37e34e69f53bb335bc
--- /dev/null
+++ b/GeoModelCore/GeoModelHelpers/GeoModelHelpers/printVolume.h
@@ -0,0 +1,11 @@
+/*
+  Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
+*/
+#ifndef GEOMODELUTILS_PRINTVOLUME_H
+#define GEOMODELUTILS_PRINTVOLUME_H
+#include "GeoModelKernel/GeoVPhysVol.h"
+
+/** @brief creates a string to print the volume */
+std::string printVolume(const PVConstLink& volume);
+
+#endif
\ No newline at end of file
diff --git a/GeoModelCore/GeoModelHelpers/src/printVolume.cxx b/GeoModelCore/GeoModelHelpers/src/printVolume.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..6a837c1d35d8de491ed0e65143adb0f1b4a83c74
--- /dev/null
+++ b/GeoModelCore/GeoModelHelpers/src/printVolume.cxx
@@ -0,0 +1,55 @@
+/*
+  Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
+*/
+#include "GeoModelKernel/GeoFullPhysVol.h"
+
+#include "GeoModelHelpers/printVolume.h"
+#include "GeoModelHelpers/GeoShapeUtils.h"
+#include "GeoModelHelpers/getChildNodesWithTrf.h"
+#include "GeoModelHelpers/StringUtils.h"
+#include "GeoModelHelpers/TransformToStringConverter.h"
+#include <sstream>
+
+constexpr unsigned indentStep =2;
+
+std::string printVolume(const PVConstLink& volume, const unsigned childIndent);
+
+std::string printVolume(const PVConstLink& volume) {
+    return printVolume(volume, indentStep);
+}
+
+
+std::string printVolume(const PVConstLink& volume, const unsigned childIndent) {
+    std::stringstream outStr{};
+    
+    if (volume->isShared()) {
+        outStr<<"shared volume ("<<(volume->refCount()-1)<<") -- ";
+    } else {
+        const GeoVPhysVol* pv = volume;
+        if(typeid(*pv) == typeid(GeoFullPhysVol)) {
+            outStr<<"fullPhysVol -- ";
+            outStr<<"abs. position: "<<GeoTrf::toString(static_cast<const GeoFullPhysVol&>(*volume).getAbsoluteTransform(),true)<<", ";
+        }
+        outStr<<"rel. position: "<<GeoTrf::toString(volume->getX(), true)<<", "; 
+    }
+    outStr<<"logical volume: "<<volume->getLogVol()->getName()<<", ";
+    outStr<<"material: "<<volume->getLogVol()->getMaterial()->getName()<<", ";
+    outStr<<"shape: "<<printGeoShape(volume->getLogVol()->getShape());
+    if (!volume->getNChildVols()) {
+        return outStr.str();
+    }
+    outStr<<", # children: "<<volume->getNChildVols()<<std::endl;
+    std::vector<GeoChildNodeWithTrf> children = getChildrenWithRef(volume, false);
+    for (unsigned int ch = 0 ; ch < children.size(); ++ch) {
+        const GeoChildNodeWithTrf& child{children[ch]};
+        outStr<<GeoStrUtils::whiteSpaces(childIndent, " ")<<(ch+1)<<"): ";
+        if (child.nodeName != child.volume->getLogVol()->getName()){
+            outStr<<"<"<<child.nodeName<<">  ";
+        }
+        if (child.volume->isShared()) {
+            outStr<<"rel. position: "<<GeoTrf::toString(child.transform, true)<<", "; 
+        }
+        outStr<<printVolume(children[ch].volume, childIndent + indentStep)<<std::endl;
+    }
+    return outStr.str();
+}
\ No newline at end of file