diff --git a/source/RobotAPI/libraries/ArmarXObjects/ObjectFinder.cpp b/source/RobotAPI/libraries/ArmarXObjects/ObjectFinder.cpp
index 0be8ac4ead84c01f2fdb095d20e6fc79ee7f636f..e9a9fc4ebc70dbcf0a0761ccd75f7cc21d4609d1 100644
--- a/source/RobotAPI/libraries/ArmarXObjects/ObjectFinder.cpp
+++ b/source/RobotAPI/libraries/ArmarXObjects/ObjectFinder.cpp
@@ -1,5 +1,6 @@
 #include <VirtualRobot/XML/ObjectIO.h>
 
+#include <SimoxUtility/algorithm/string.h>
 #include <SimoxUtility/filesystem/list_directory.h>
 
 #include <ArmarXCore/core/system/ArmarXDataPath.h>
@@ -229,6 +230,43 @@ namespace armarx
         return loadObstacle(findObject(obj));
     }
 
+
+    static std::vector<std::string> _loadNames(
+            const ObjectFinder& finder,
+            const ObjectID& objectID,
+            const bool includeClassName,
+            const std::function<std::optional<std::vector<std::string>>(const ObjectInfo&)> loadNamesFn)
+    {
+        std::vector<std::string> names;
+        if (includeClassName)
+        {
+            names.push_back(simox::alg::to_lower(objectID.className()));
+        }
+        if (std::optional<ObjectInfo> info = finder.findObject(objectID))
+        {
+            if (std::optional<std::vector<std::string>> loadedNames = loadNamesFn(*info))
+            {
+                // Source: https://stackoverflow.com/a/201729
+                names.insert(names.end(), loadedNames->begin(), loadedNames->end());
+            }
+        }
+        return names;
+    }
+    std::vector<std::string> ObjectFinder::loadRecognizedNames(const ObjectID& objectID, bool includeClassName) const
+    {
+        return _loadNames(*this, objectID, includeClassName, [](const ObjectInfo& info)
+        {
+            return info.loadRecognizedNames();
+        });
+    }
+    std::vector<std::string> ObjectFinder::loadSpokenNames(const ObjectID& objectID, bool includeClassName) const
+    {
+        return _loadNames(*this, objectID, includeClassName, [](const ObjectInfo& info)
+        {
+            return info.loadSpokenNames();
+        });
+    }
+
     ObjectFinder::path ObjectFinder::_rootDirAbs() const
     {
         return packageDataDir / packageName;
diff --git a/source/RobotAPI/libraries/ArmarXObjects/ObjectFinder.h b/source/RobotAPI/libraries/ArmarXObjects/ObjectFinder.h
index bb503090e966c43b7fb60bb47a90b0fee1cdaf97..f6e9831593f4a8714eb4f3c461016bce063cadaa 100644
--- a/source/RobotAPI/libraries/ArmarXObjects/ObjectFinder.h
+++ b/source/RobotAPI/libraries/ArmarXObjects/ObjectFinder.h
@@ -53,6 +53,32 @@ namespace armarx
         static loadObstacle(const std::optional<ObjectInfo>& ts);
         VirtualRobot::ObstaclePtr
         loadObstacle(const objpose::ObjectPose& obj) const;
+
+
+        /**
+         * @brief Load names to use when matched when recognizing an object by name.
+         *
+         * If the object's names JSON file does not exist, no names will be added from a file.
+         * If you would like to detect this case, first `findObject()`, then use
+         * `ObjectInfo::loadRecognizedNames()`, which returns a `std::optional`.
+         *
+         * @param includeClassName If true, include the raw class name in the result.
+         * @see `ObjectInfo::loadRecognizedNames()`
+         */
+        std::vector<std::string> loadRecognizedNames(const ObjectID& objectID, bool includeClassName = false) const;
+        /**
+         * @brief Load names to use when verbalizing an object name.
+         *
+         * If the object's names JSON file does not exist, no names will be added from a file.
+         * If you would like to detect this case, first `findObject()`, then use
+         * `ObjectInfo::loadSpokenNames()`, which returns a `std::optional`.
+         *
+         * @param includeClassName If true, include the raw class name in the result.
+         * @see `ObjectInfo::loadSpokenNames()`
+         */
+        std::vector<std::string> loadSpokenNames(const ObjectID& objectID, bool includeClassName = false) const;
+
+
     private:
         void init() const;
 
diff --git a/source/RobotAPI/libraries/ArmarXObjects/ObjectInfo.cpp b/source/RobotAPI/libraries/ArmarXObjects/ObjectInfo.cpp
index 1dc1903ea050d8c2f9fba6596d04fc12d197f5ba..8c70a93aac07dae51aa5b82014038e009f2a8cd1 100644
--- a/source/RobotAPI/libraries/ArmarXObjects/ObjectInfo.cpp
+++ b/source/RobotAPI/libraries/ArmarXObjects/ObjectInfo.cpp
@@ -1,5 +1,6 @@
 #include "ObjectInfo.h"
 
+#include <SimoxUtility/algorithm/string.h>
 #include <SimoxUtility/json.h>
 #include <SimoxUtility/shapes/AxisAlignedBoundingBox.h>
 #include <SimoxUtility/shapes/OrientedBox.h>
@@ -85,6 +86,11 @@ namespace armarx
         return file(".json", "_bb");
     }
 
+    PackageFileLocation ObjectInfo::namesJson() const
+    {
+        return file(".json", "_names");
+    }
+
     std::optional<simox::AxisAlignedBoundingBox> ObjectInfo::loadAABB() const
     {
         nlohmann::json j;
@@ -147,6 +153,47 @@ namespace armarx
         return oobb;
     }
 
+    std::optional<std::vector<std::string>> ObjectInfo::loadRecognizedNames() const
+    {
+        return loadNames("recognized_name");
+    }
+
+    std::optional<std::vector<std::string>> ObjectInfo::loadSpokenNames() const
+    {
+        return loadNames("spoken_name");
+    }
+
+    std::optional<std::vector<std::string> > ObjectInfo::loadNames(const std::string& jsonKey) const
+    {
+        const PackageFileLocation file = namesJson();
+        if (fs::is_regular_file(file.absolutePath))
+        {
+            nlohmann::json json;
+
+            try
+            {
+                json = nlohmann::read_json(file.absolutePath);
+            }
+            catch (const nlohmann::json::exception& e)
+            {
+                ARMARX_WARNING << "Failed to parse JSON file " << file.absolutePath << ": \n" << e.what();
+                return std::nullopt;
+            }
+            catch (const std::exception& e)
+            {
+                ARMARX_WARNING << "Failed to read file " << file.absolutePath << ": \n" << e.what();
+                return std::nullopt;
+            }
+
+            return json.at(jsonKey).get<std::vector<std::string>>();
+        }
+        else
+        {
+            return std::nullopt;
+        }
+    }
+
+
     bool ObjectInfo::checkPaths() const
     {
         namespace fs = std::filesystem;
diff --git a/source/RobotAPI/libraries/ArmarXObjects/ObjectInfo.h b/source/RobotAPI/libraries/ArmarXObjects/ObjectInfo.h
index 5f50ec95691bb881b00be5e64703f39c9e9e7b02..9888a4c7a2d448e9d6c5f81bfd3d849039776428 100644
--- a/source/RobotAPI/libraries/ArmarXObjects/ObjectInfo.h
+++ b/source/RobotAPI/libraries/ArmarXObjects/ObjectInfo.h
@@ -64,6 +64,10 @@ namespace armarx
 
         PackageFileLocation boundingBoxJson() const;
 
+        /// File containing recognized and spoken names of objects.
+        PackageFileLocation namesJson() const;
+
+
         /**
          * @brief Load the AABB (axis-aligned bounding-box) from the bounding box JSON file.
          * @return Return the AABB if successful, `std::nullopt` if file does not exist.
@@ -76,6 +80,17 @@ namespace armarx
          */
         std::optional<simox::OrientedBox<float>> loadOOBB() const;
 
+        /**
+         * @brief Load names to use when matched when recognizing an object by name.
+         * @see `namesJson()`
+         */
+        std::optional<std::vector<std::string>> loadRecognizedNames() const;
+        /**
+         * @brief Load names to use when verbalizing an object name.
+         * @see `namesJson()`
+         */
+        std::optional<std::vector<std::string>> loadSpokenNames() const;
+
 
         /**
          * @brief Checks the existence of expected files.
@@ -88,6 +103,7 @@ namespace armarx
     private:
 
         path objectDirectory() const;
+        std::optional<std::vector<std::string>> loadNames(const std::string& jsonKey) const;
 
 
     private: