diff --git a/composer.json b/composer.json
index 086fc39ae02dd00a04b95cc06d98a279826d6340..8dd193e5ba2442cba0e1d3941e1e74b974a1acf0 100644
--- a/composer.json
+++ b/composer.json
@@ -56,6 +56,7 @@
 		"symfony/finder": "^4.1",
 		"symfony/polyfill-intl-icu": "^1.6",
 		"symfony/polyfill-mbstring": "^1.2",
+		"symfony/property-access": "^4.2",
 		"symfony/property-info": "^4.2",
 		"symfony/routing": "^4.1",
 		"symfony/yaml": "^4.1",
diff --git a/composer.lock b/composer.lock
index b3c65e42d177b2dc63e38ffa24998a49d7eeadca..59cbe890fdb4ea0c96fdd85bb1f6efd7bf9fabdd 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "665468d5ffefa8e2e72be0a338838765",
+    "content-hash": "ae4664b4ee7bf0aa9a614d355d67db78",
     "packages": [
         {
             "name": "cogpowered/finediff",
@@ -2246,6 +2246,73 @@
             ],
             "time": "2018-08-06T14:22:27+00:00"
         },
+        {
+            "name": "symfony/property-access",
+            "version": "v4.2.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/property-access.git",
+                "reference": "a21d40670000f61a1a4b90a607d54696aad914cd"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/property-access/zipball/a21d40670000f61a1a4b90a607d54696aad914cd",
+                "reference": "a21d40670000f61a1a4b90a607d54696aad914cd",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3",
+                "symfony/inflector": "~3.4|~4.0"
+            },
+            "require-dev": {
+                "symfony/cache": "~3.4|~4.0"
+            },
+            "suggest": {
+                "psr/cache-implementation": "To cache access methods."
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.2-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\PropertyAccess\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony PropertyAccess Component",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "access",
+                "array",
+                "extraction",
+                "index",
+                "injection",
+                "object",
+                "property",
+                "property path",
+                "reflection"
+            ],
+            "time": "2019-01-05T16:37:49+00:00"
+        },
         {
             "name": "symfony/property-info",
             "version": "v4.2.2",
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-87332-AvoidRuntimeReflectionCallsInObjectAccess.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-87332-AvoidRuntimeReflectionCallsInObjectAccess.rst
new file mode 100644
index 0000000000000000000000000000000000000000..9b29faacec63a07537534a0bb7139e4801678796
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-87332-AvoidRuntimeReflectionCallsInObjectAccess.rst
@@ -0,0 +1,40 @@
+.. include:: ../../Includes.txt
+
+====================================================================
+Deprecation: #87332 - Avoid runtime reflection calls in ObjectAccess
+====================================================================
+
+See :issue:`87332`
+
+Description
+===========
+
+1) Class :php:`\TYPO3\CMS\Extbase\Reflection\ObjectAccess` uses reflection to make non public properties gettable and settable. This behaviour is triggered by setting the argument :php:`$forceDirectAccess` of methods :php:`getProperty`, :php:`getPropertyInternal` or :php:`setProperty` to :php:`true`. Triggering this behaviour is deprecated and will be removed in TYPO3 11.0.
+
+2) Method :php:`\TYPO3\CMS\Extbase\Reflection\ObjectAccess::buildSetterMethodName` has been deprecated and will be removed in TYPO3 11.0.
+
+
+Impact
+======
+
+1) Accessing non public properties via the mentioned methods will no longer work in TYPO3 11.0.
+
+2) Calling :php:`\TYPO3\CMS\Extbase\Reflection\ObjectAccess::buildSetterMethodName` will no longer work in TYPO3 11.0.
+
+
+Affected Installations
+======================
+
+1) All installations that use the mentioned methods with argument :php:`$forceDirectAccess` set to :php:`true`.
+
+2) All installations that call :php:`\TYPO3\CMS\Extbase\Reflection\ObjectAccess::buildSetterMethodName`.
+
+
+Migration
+=========
+
+1) Make sure the affected property is accessible by either making it public or providing getters/hassers/issers or setters (:php:`getProperty()`, :php:`hasProperty()`, :php:`isProperty()`, :php:`setProperty()`).
+
+2) Build setter names manually: :php:`$setterMethodName = 'set' . ucfirst($propertyName);`
+
+.. index:: PHP-API, FullyScanned, ext:extbase
diff --git a/typo3/sysext/extbase/Classes/Property/TypeConverter/ObjectConverter.php b/typo3/sysext/extbase/Classes/Property/TypeConverter/ObjectConverter.php
index 8f01d76c594e2db2a1bc02608cd69bedbf49d122..0d6e9d7148bedfc700412d370faab109a8d09c1d 100644
--- a/typo3/sysext/extbase/Classes/Property/TypeConverter/ObjectConverter.php
+++ b/typo3/sysext/extbase/Classes/Property/TypeConverter/ObjectConverter.php
@@ -16,7 +16,6 @@ namespace TYPO3\CMS\Extbase\Property\TypeConverter;
 
 use TYPO3\CMS\Extbase\Reflection\ClassSchema\Exception\NoSuchMethodException;
 use TYPO3\CMS\Extbase\Reflection\ClassSchema\Exception\NoSuchMethodParameterException;
-use TYPO3\CMS\Extbase\Reflection\ClassSchema\MethodParameter;
 
 /**
  * This converter transforms arrays to simple objects (POPO) by setting properties.
@@ -122,9 +121,9 @@ class ObjectConverter extends AbstractTypeConverter
         $specificTargetType = $this->objectContainer->getImplementationClassName($targetType);
         $classSchema = $this->reflectionService->getClassSchema($specificTargetType);
 
-        if ($classSchema->hasMethod(\TYPO3\CMS\Extbase\Reflection\ObjectAccess::buildSetterMethodName($propertyName))) {
-            $methodParameters = $classSchema->getMethod(\TYPO3\CMS\Extbase\Reflection\ObjectAccess::buildSetterMethodName($propertyName))->getParameters();
-            /** @var MethodParameter $methodParameter */
+        $methodName = 'set' . ucfirst($propertyName);
+        if ($classSchema->hasMethod($methodName)) {
+            $methodParameters = $classSchema->getMethod($methodName)->getParameters() ?? [];
             $methodParameter = current($methodParameters);
             if ($methodParameter->getType() === null) {
                 throw new \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException('Setter for property "' . $propertyName . '" had no type hint or documentation in target object of type "' . $specificTargetType . '".', 1303379158);
diff --git a/typo3/sysext/extbase/Classes/Reflection/ClassSchema.php b/typo3/sysext/extbase/Classes/Reflection/ClassSchema.php
index 66e3dd8acffc026927075b441b521dea131a7507..4aa48608c7219f2d1b062d3f16262654aceb2cbc 100644
--- a/typo3/sysext/extbase/Classes/Reflection/ClassSchema.php
+++ b/typo3/sysext/extbase/Classes/Reflection/ClassSchema.php
@@ -560,7 +560,7 @@ class ClassSchema
     }
 
     /**
-     * @return array
+     * @return array|Method[]
      */
     public function getMethods(): array
     {
diff --git a/typo3/sysext/extbase/Classes/Reflection/ObjectAccess.php b/typo3/sysext/extbase/Classes/Reflection/ObjectAccess.php
index aa15bab73c70507a2853f40dbd0136b03ec33ae8..dd5699d79bb4ea95da8931765eb386b8e36accd5 100644
--- a/typo3/sysext/extbase/Classes/Reflection/ObjectAccess.php
+++ b/typo3/sysext/extbase/Classes/Reflection/ObjectAccess.php
@@ -1,4 +1,6 @@
 <?php
+declare(strict_types = 1);
+
 namespace TYPO3\CMS\Extbase\Reflection;
 
 /*
@@ -14,6 +16,11 @@ namespace TYPO3\CMS\Extbase\Reflection;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Symfony\Component\PropertyAccess\PropertyAccess;
+use Symfony\Component\PropertyAccess\PropertyAccessor;
+use Symfony\Component\PropertyAccess\PropertyPath;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\StringUtility;
 use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
 
 /**
@@ -27,11 +34,10 @@ use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
  */
 class ObjectAccess
 {
-    const ACCESS_GET = 0;
-
-    const ACCESS_SET = 1;
-
-    const ACCESS_PUBLIC = 2;
+    /**
+     * @var PropertyAccessor
+     */
+    private static $propertyAccessor;
 
     /**
      * Get a property of a given object.
@@ -49,16 +55,18 @@ class ObjectAccess
      * @param bool $forceDirectAccess directly access property using reflection(!)
      *
      * @throws \InvalidArgumentException in case $subject was not an object or $propertyName was not a string
+     * @throws Exception\PropertyNotAccessibleException
      * @return mixed Value of the property
      */
-    public static function getProperty($subject, $propertyName, $forceDirectAccess = false)
+    public static function getProperty($subject, string $propertyName, bool $forceDirectAccess = false)
     {
         if (!is_object($subject) && !is_array($subject)) {
-            throw new \InvalidArgumentException('$subject must be an object or array, ' . gettype($subject) . ' given.', 1237301367);
-        }
-        if (!is_string($propertyName) && (!is_array($subject) && !$subject instanceof \ArrayAccess)) {
-            throw new \InvalidArgumentException('Given property name is not of type string.', 1231178303);
+            throw new \InvalidArgumentException(
+                '$subject must be an object or array, ' . gettype($subject) . ' given.',
+                1237301367
+            );
         }
+
         return self::getPropertyInternal($subject, $propertyName, $forceDirectAccess);
     }
 
@@ -77,52 +85,38 @@ class ObjectAccess
      * @return mixed Value of the property
      * @internal
      */
-    public static function getPropertyInternal($subject, $propertyName, $forceDirectAccess = false)
+    public static function getPropertyInternal($subject, string $propertyName, bool $forceDirectAccess = false)
     {
-        // type check and conversion of iterator to numerically indexed array
-        if ($subject === null || is_scalar($subject)) {
-            return null;
+        if ($forceDirectAccess === true) {
+            trigger_error('Argument $forceDirectAccess will be removed in TYPO3 11.0', E_USER_DEPRECATED);
         }
+
         if (!$forceDirectAccess && ($subject instanceof \SplObjectStorage || $subject instanceof ObjectStorage)) {
             $subject = iterator_to_array(clone $subject, false);
         }
 
-        // value get based on data type of $subject (possibly converted above)
-        if (($subject instanceof \ArrayAccess && $subject->offsetExists($propertyName)) || is_array($subject)) {
-            // isset() is safe; array_key_exists would only be needed to determine
-            // if the value is NULL - and that's already what we return as fallback.
-            if (isset($subject[$propertyName])) {
-                return $subject[$propertyName];
-            }
-        } elseif (is_object($subject)) {
-            if ($forceDirectAccess) {
-                if (property_exists($subject, $propertyName)) {
-                    $propertyReflection = new \ReflectionProperty($subject, $propertyName);
-                    if ($propertyReflection->isPublic()) {
-                        return $propertyReflection->getValue($subject);
-                    }
-                    $propertyReflection->setAccessible(true);
-                    return $propertyReflection->getValue($subject);
-                }
-                throw new Exception\PropertyNotAccessibleException('The property "' . $propertyName . '" on the subject does not exist.', 1302855001);
-            }
-            $upperCasePropertyName = ucfirst($propertyName);
-            $getterMethodName = 'get' . $upperCasePropertyName;
-            if (is_callable([$subject, $getterMethodName])) {
-                return $subject->{$getterMethodName}();
-            }
-            $getterMethodName = 'is' . $upperCasePropertyName;
-            if (is_callable([$subject, $getterMethodName])) {
-                return $subject->{$getterMethodName}();
-            }
-            $getterMethodName = 'has' . $upperCasePropertyName;
-            if (is_callable([$subject, $getterMethodName])) {
-                return $subject->{$getterMethodName}();
-            }
-            if (property_exists($subject, $propertyName)) {
-                return $subject->{$propertyName};
+        $propertyPath = new PropertyPath($propertyName);
+
+        if ($subject instanceof \ArrayAccess) {
+            $accessor = self::createAccessor();
+
+            // Check if $subject is an instance of \ArrayAccess and therefore maybe has actual accessible properties.
+            if ($accessor->isReadable($subject, $propertyPath)) {
+                return $accessor->getValue($subject, $propertyPath);
             }
-            throw new Exception\PropertyNotAccessibleException('The property "' . $propertyName . '" on the subject does not exist.', 1476109666);
+
+            // Use array style property path for instances of \ArrayAccess
+            // https://symfony.com/doc/current/components/property_access.html#reading-from-arrays
+
+            $propertyPath = self::convertToArrayPropertyPath($propertyPath);
+        }
+
+        if (is_object($subject)) {
+            return self::getObjectPropertyValue($subject, $propertyPath, $forceDirectAccess);
+        }
+
+        if (is_array($subject)) {
+            return self::getArrayIndexValue($subject, self::convertToArrayPropertyPath($propertyPath));
         }
 
         return null;
@@ -141,11 +135,10 @@ class ObjectAccess
      *
      * @return mixed Value of the property
      */
-    public static function getPropertyPath($subject, $propertyPath)
+    public static function getPropertyPath($subject, string $propertyPath)
     {
-        $propertyPathSegments = explode('.', $propertyPath);
         try {
-            foreach ($propertyPathSegments as $pathSegment) {
+            foreach (new PropertyPath($propertyPath) as $pathSegment) {
                 $subject = self::getPropertyInternal($subject, $pathSegment);
             }
         } catch (Exception\PropertyNotAccessibleException $error) {
@@ -173,8 +166,12 @@ class ObjectAccess
      * @throws \InvalidArgumentException in case $object was not an object or $propertyName was not a string
      * @return bool TRUE if the property could be set, FALSE otherwise
      */
-    public static function setProperty(&$subject, $propertyName, $propertyValue, $forceDirectAccess = false)
+    public static function setProperty(&$subject, string $propertyName, $propertyValue, bool $forceDirectAccess = false): bool
     {
+        if ($forceDirectAccess === true) {
+            trigger_error('Argument $forceDirectAccess will be removed in TYPO3 11.0', E_USER_DEPRECATED);
+        }
+
         if (is_array($subject) || ($subject instanceof \ArrayAccess && !$forceDirectAccess)) {
             $subject[$propertyName] = $propertyValue;
             return true;
@@ -182,10 +179,13 @@ class ObjectAccess
         if (!is_object($subject)) {
             throw new \InvalidArgumentException('subject must be an object or array, ' . gettype($subject) . ' given.', 1237301368);
         }
-        if (!is_string($propertyName)) {
-            throw new \InvalidArgumentException('Given property name is not of type string.', 1231178878);
+
+        $accessor = self::createAccessor();
+        if ($accessor->isWritable($subject, $propertyName)) {
+            $accessor->setValue($subject, $propertyName, $propertyValue);
+            return true;
         }
-        $result = true;
+
         if ($forceDirectAccess) {
             if (property_exists($subject, $propertyName)) {
                 $propertyReflection = new \ReflectionProperty($subject, $propertyName);
@@ -194,22 +194,11 @@ class ObjectAccess
             } else {
                 $subject->{$propertyName} = $propertyValue;
             }
-            return $result;
-        }
-        $setterMethodName = self::buildSetterMethodName($propertyName);
-        if (is_callable([$subject, $setterMethodName])) {
-            $subject->{$setterMethodName}($propertyValue);
-        } elseif (property_exists($subject, $propertyName)) {
-            $reflection = new \ReflectionProperty($subject, $propertyName);
-            if ($reflection->isPublic()) {
-                $subject->{$propertyName} = $propertyValue;
-            } else {
-                $result = false;
-            }
-        } else {
-            $result = false;
+
+            return true;
         }
-        return $result;
+
+        return false;
     }
 
     /**
@@ -221,51 +210,55 @@ class ObjectAccess
      *
      * @param object $object Object to receive property names for
      *
-     * @throws \InvalidArgumentException
      * @return array Array of all gettable property names
+     * @throws Exception\UnknownClassException
      */
-    public static function getGettablePropertyNames($object)
+    public static function getGettablePropertyNames(object $object): array
     {
-        if (!is_object($object)) {
-            throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1237301369);
-        }
         if ($object instanceof \stdClass) {
             $properties = array_keys((array)$object);
             sort($properties);
             return $properties;
         }
 
-        $reflection = new \ReflectionClass($object);
-        $declaredPropertyNames = array_map(
-            function (\ReflectionProperty $property) {
-                return $property->getName();
-            },
-            $reflection->getProperties(\ReflectionProperty::IS_PUBLIC)
-        );
-        foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
-            $methodParameters = $method->getParameters();
-            if (!empty($methodParameters)) {
-                foreach ($methodParameters as $parameter) {
-                    if (!$parameter->isOptional()) {
-                        continue 2;
-                    }
+        $classSchema = GeneralUtility::makeInstance(ReflectionService::class)
+            ->getClassSchema($object);
+
+        $accessor = self::createAccessor();
+        $propertyNames = array_keys($classSchema->getProperties());
+        $accessiblePropertyNames = array_filter($propertyNames, function ($propertyName) use ($accessor, $object) {
+            return $accessor->isReadable($object, $propertyName);
+        });
+
+        foreach ($classSchema->getMethods() as $methodName => $methodDefinition) {
+            if (!$methodDefinition->isPublic()) {
+                continue;
+            }
+
+            foreach ($methodDefinition->getParameters() as $methodParam) {
+                if (!$methodParam->isOptional()) {
+                    continue 2;
                 }
             }
-            $methodName = $method->getName();
-            if (strpos($methodName, 'is') === 0) {
-                $declaredPropertyNames[] = lcfirst(substr($methodName, 2));
+
+            if (StringUtility::beginsWith($methodName, 'get')) {
+                $accessiblePropertyNames[] = lcfirst(substr($methodName, 3));
+                continue;
             }
-            if (strpos($methodName, 'get') === 0) {
-                $declaredPropertyNames[] = lcfirst(substr($methodName, 3));
+
+            if (StringUtility::beginsWith($methodName, 'has')) {
+                $accessiblePropertyNames[] = lcfirst(substr($methodName, 3));
+                continue;
             }
-            if (strpos($methodName, 'has') === 0) {
-                $declaredPropertyNames[] = lcfirst(substr($methodName, 3));
+
+            if (StringUtility::beginsWith($methodName, 'is')) {
+                $accessiblePropertyNames[] = lcfirst(substr($methodName, 2));
             }
         }
-        $propertyNames = array_unique($declaredPropertyNames);
-        sort($propertyNames);
 
-        return $propertyNames;
+        $accessiblePropertyNames = array_unique($accessiblePropertyNames);
+        sort($accessiblePropertyNames);
+        return $accessiblePropertyNames;
     }
 
     /**
@@ -280,22 +273,29 @@ class ObjectAccess
      * @throws \InvalidArgumentException
      * @return array Array of all settable property names
      */
-    public static function getSettablePropertyNames($object)
+    public static function getSettablePropertyNames(object $object): array
     {
-        if (!is_object($object)) {
-            throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1264022994);
-        }
-        if ($object instanceof \stdClass) {
-            $declaredPropertyNames = array_keys((array)$object);
+        $accessor = self::createAccessor();
+
+        if ($object instanceof \stdClass || $object instanceof \ArrayAccess) {
+            $propertyNames = array_keys((array)$object);
         } else {
-            $declaredPropertyNames = array_keys(get_class_vars(get_class($object)));
-        }
-        foreach (get_class_methods($object) as $methodName) {
-            if (strpos($methodName, 'set') === 0 && is_callable([$object, $methodName])) {
-                $declaredPropertyNames[] = lcfirst(substr($methodName, 3));
+            $classSchema = GeneralUtility::makeInstance(ReflectionService::class)->getClassSchema($object);
+
+            $propertyNames = array_filter(array_keys($classSchema->getProperties()), function ($methodName) use ($accessor, $object) {
+                return $accessor->isWritable($object, $methodName);
+            });
+
+            $setters = array_filter(array_keys($classSchema->getMethods()), function ($methodName) use ($object) {
+                return StringUtility::beginsWith($methodName, 'set') && is_callable([$object, $methodName]);
+            });
+
+            foreach ($setters as $setter) {
+                $propertyNames[] = lcfirst(substr($setter, 3));
             }
         }
-        $propertyNames = array_unique($declaredPropertyNames);
+
+        $propertyNames = array_unique($propertyNames);
         sort($propertyNames);
         return $propertyNames;
     }
@@ -303,24 +303,21 @@ class ObjectAccess
     /**
      * Tells if the value of the specified property can be set by this Object Accessor.
      *
-     * @param object $object Object containting the property
+     * @param object $object Object containing the property
      * @param string $propertyName Name of the property to check
      *
      * @throws \InvalidArgumentException
      * @return bool
      */
-    public static function isPropertySettable($object, $propertyName)
+    public static function isPropertySettable(object $object, $propertyName): bool
     {
-        if (!is_object($object)) {
-            throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1259828920);
-        }
         if ($object instanceof \stdClass && array_key_exists($propertyName, get_object_vars($object))) {
             return true;
         }
         if (array_key_exists($propertyName, get_class_vars(get_class($object)))) {
             return true;
         }
-        return is_callable([$object, self::buildSetterMethodName($propertyName)]);
+        return is_callable([$object, 'set' . ucfirst($propertyName)]);
     }
 
     /**
@@ -332,31 +329,17 @@ class ObjectAccess
      * @throws \InvalidArgumentException
      * @return bool
      */
-    public static function isPropertyGettable($object, $propertyName)
+    public static function isPropertyGettable($object, $propertyName): bool
     {
-        if (!is_object($object)) {
-            throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1259828921);
-        }
-        if ($object instanceof \ArrayAccess && isset($object[$propertyName])) {
-            return true;
-        }
-        if ($object instanceof \stdClass && isset($object->$propertyName)) {
-            return true;
+        if (($object instanceof \ArrayAccess) && !$object->offsetExists($propertyName)) {
+            return false;
         }
-        if (is_callable([$object, 'get' . ucfirst($propertyName)])) {
-            return true;
-        }
-        if (is_callable([$object, 'has' . ucfirst($propertyName)])) {
-            return true;
-        }
-        if (is_callable([$object, 'is' . ucfirst($propertyName)])) {
-            return true;
-        }
-        if (property_exists($object, $propertyName)) {
-            $propertyReflection = new \ReflectionProperty($object, $propertyName);
-            return $propertyReflection->isPublic();
+
+        if (is_array($object) || $object instanceof \ArrayAccess) {
+            $propertyName = self::wrap($propertyName);
         }
-        return false;
+
+        return self::createAccessor()->isReadable($object, $propertyName);
     }
 
     /**
@@ -369,11 +352,8 @@ class ObjectAccess
      * @return array Associative array of all properties.
      * @todo What to do with ArrayAccess
      */
-    public static function getGettableProperties($object)
+    public static function getGettableProperties(object $object): array
     {
-        if (!is_object($object)) {
-            throw new \InvalidArgumentException('$object must be an object, ' . gettype($object) . ' given.', 1237301370);
-        }
         $properties = [];
         foreach (self::getGettablePropertyNames($object) as $propertyName) {
             $properties[$propertyName] = self::getPropertyInternal($object, $propertyName);
@@ -388,9 +368,88 @@ class ObjectAccess
      * @param string $propertyName Name of the property
      *
      * @return string Name of the setter method name
+     * @deprecated
      */
-    public static function buildSetterMethodName($propertyName)
+    public static function buildSetterMethodName($propertyName): string
     {
+        trigger_error(__METHOD__ . ' will be removed in TYPO3 11.0', E_USER_DEPRECATED);
+
         return 'set' . ucfirst($propertyName);
     }
+
+    /**
+     * @return PropertyAccessor
+     */
+    private static function createAccessor(): PropertyAccessor
+    {
+        if (static::$propertyAccessor === null) {
+            static::$propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
+                ->getPropertyAccessor();
+        }
+
+        return static::$propertyAccessor;
+    }
+
+    /**
+     * @param object $subject
+     * @param PropertyPath $propertyPath
+     * @param bool $forceDirectAccess
+     * @return mixed
+     * @throws Exception\PropertyNotAccessibleException
+     * @throws \ReflectionException
+     */
+    private static function getObjectPropertyValue(object $subject, PropertyPath $propertyPath, bool $forceDirectAccess)
+    {
+        $accessor = self::createAccessor();
+
+        if ($accessor->isReadable($subject, $propertyPath)) {
+            return $accessor->getValue($subject, $propertyPath);
+        }
+
+        $propertyName = (string)$propertyPath;
+
+        if (!$forceDirectAccess) {
+            throw new Exception\PropertyNotAccessibleException('The property "' . $propertyName . '" on the subject does not exist.', 1476109666);
+        }
+
+        if (!property_exists($subject, $propertyName)) {
+            throw new Exception\PropertyNotAccessibleException('The property "' . $propertyName . '" on the subject does not exist.', 1302855001);
+        }
+
+        $propertyReflection = new \ReflectionProperty($subject, $propertyName);
+        $propertyReflection->setAccessible(true);
+        return $propertyReflection->getValue($subject);
+    }
+
+    /**
+     * @param array $subject
+     * @param PropertyPath $propertyPath
+     * @return mixed
+     */
+    private static function getArrayIndexValue(array $subject, PropertyPath $propertyPath)
+    {
+        return self::createAccessor()->getValue($subject, $propertyPath);
+    }
+
+    /**
+     * @param PropertyPath $propertyPath
+     * @return PropertyPath
+     */
+    private static function convertToArrayPropertyPath(PropertyPath $propertyPath): PropertyPath
+    {
+        $segments = array_map(function ($segment) {
+            return static::wrap($segment);
+        }, $propertyPath->getElements());
+
+        return new PropertyPath(implode('.', $segments));
+    }
+
+    /**
+     * @param string $segment
+     * @return string
+     */
+    private static function wrap(string $segment): string
+    {
+        return '[' . $segment . ']';
+    }
 }
diff --git a/typo3/sysext/extbase/Classes/Validation/Validator/GenericObjectValidator.php b/typo3/sysext/extbase/Classes/Validation/Validator/GenericObjectValidator.php
index def5f15fbe4e0c7717c5202bf374974bc774f4f5..2448c4933a945428a98d225ebb05ce063469b207 100644
--- a/typo3/sysext/extbase/Classes/Validation/Validator/GenericObjectValidator.php
+++ b/typo3/sysext/extbase/Classes/Validation/Validator/GenericObjectValidator.php
@@ -67,7 +67,15 @@ class GenericObjectValidator extends AbstractValidator implements ObjectValidato
         if (ObjectAccess::isPropertyGettable($object, $propertyName)) {
             return ObjectAccess::getProperty($object, $propertyName);
         }
-        return ObjectAccess::getProperty($object, $propertyName, true);
+        throw new \RuntimeException(
+            sprintf(
+                'Could not get value of property "%s::%s", make sure the property is either public or has a getter get%3$s(), a hasser has%3$s() or an isser is%3$s().',
+                get_class($object),
+                $propertyName,
+                ucfirst($propertyName)
+            ),
+            1546632293
+        );
     }
 
     /**
diff --git a/typo3/sysext/extbase/Tests/Unit/Mvc/View/JsonViewTest.php b/typo3/sysext/extbase/Tests/Unit/Mvc/View/JsonViewTest.php
index 90b30f3808c4143d7f6bb5b2d9928f669ec855e9..fdd03e36fc43e5aea2b175e36214a142d2bf0cfa 100644
--- a/typo3/sysext/extbase/Tests/Unit/Mvc/View/JsonViewTest.php
+++ b/typo3/sysext/extbase/Tests/Unit/Mvc/View/JsonViewTest.php
@@ -27,6 +27,8 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
  */
 class JsonViewTest extends UnitTestCase
 {
+    protected $resetSingletonInstances = true;
+
     /**
      * @var \TYPO3\CMS\Extbase\Mvc\View\JsonView
      */
diff --git a/typo3/sysext/extbase/Tests/Unit/Reflection/ObjectAccessTest.php b/typo3/sysext/extbase/Tests/Unit/Reflection/ObjectAccessTest.php
index ddcb864978c2aa3056aea8891bdf038390cfde1c..3dab3be2ec60ddc84a1d1e229bec587136b42cfb 100644
--- a/typo3/sysext/extbase/Tests/Unit/Reflection/ObjectAccessTest.php
+++ b/typo3/sysext/extbase/Tests/Unit/Reflection/ObjectAccessTest.php
@@ -24,6 +24,11 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
  */
 class ObjectAccessTest extends UnitTestCase
 {
+    /**
+     * @var bool Reset singletons created by subject
+     */
+    protected $resetSingletonInstances = true;
+
     /**
      * @var DummyClassWithGettersAndSetters
      */
@@ -58,35 +63,6 @@ class ObjectAccessTest extends UnitTestCase
         $this->assertEquals($property, 42, 'A property of a given object was not returned correctly.');
     }
 
-    /**
-     * @test
-     */
-    public function getPropertyReturnsExpectedValueForUnexposedPropertyIfForceDirectAccessIsTrue()
-    {
-        $property = ObjectAccess::getProperty($this->dummyObject, 'unexposedProperty', true);
-        $this->assertEquals($property, 'unexposed', 'A property of a given object was not returned correctly.');
-    }
-
-    /**
-     * @test
-     */
-    public function getPropertyReturnsExpectedValueForUnknownPropertyIfForceDirectAccessIsTrue()
-    {
-        $this->dummyObject->unknownProperty = 'unknown';
-        $property = ObjectAccess::getProperty($this->dummyObject, 'unknownProperty', true);
-        $this->assertEquals($property, 'unknown', 'A property of a given object was not returned correctly.');
-    }
-
-    /**
-     * @test
-     */
-    public function getPropertyThrowsPropertyNotAccessibleExceptionForNotExistingPropertyIfForceDirectAccessIsTrue()
-    {
-        $this->expectException(PropertyNotAccessibleException::class);
-        $this->expectExceptionCode(1302855001);
-        ObjectAccess::getProperty($this->dummyObject, 'notExistingProperty', true);
-    }
-
     /**
      * @test
      */
@@ -115,26 +91,6 @@ class ObjectAccessTest extends UnitTestCase
         $this->assertTrue($property);
     }
 
-    /**
-     * @test
-     */
-    public function getPropertyThrowsExceptionIfThePropertyNameIsNotAString()
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1231178303);
-        ObjectAccess::getProperty($this->dummyObject, new \ArrayObject());
-    }
-
-    /**
-     * @test
-     */
-    public function setPropertyThrowsExceptionIfThePropertyNameIsNotAString()
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1231178878);
-        ObjectAccess::setProperty($this->dummyObject, new \ArrayObject(), 42);
-    }
-
     /**
      * @test
      */
@@ -143,24 +99,6 @@ class ObjectAccessTest extends UnitTestCase
         $this->assertFalse(ObjectAccess::setProperty($this->dummyObject, 'protectedProperty', 42));
     }
 
-    /**
-     * @test
-     */
-    public function setPropertySetsValueIfPropertyIsNotAccessibleWhenForceDirectAccessIsTrue()
-    {
-        $this->assertTrue(ObjectAccess::setProperty($this->dummyObject, 'unexposedProperty', 'was set anyway', true));
-        $this->assertAttributeEquals('was set anyway', 'unexposedProperty', $this->dummyObject);
-    }
-
-    /**
-     * @test
-     */
-    public function setPropertySetsValueIfPropertyDoesNotExistWhenForceDirectAccessIsTrue()
-    {
-        $this->assertTrue(ObjectAccess::setProperty($this->dummyObject, 'unknownProperty', 'was set anyway', true));
-        $this->assertAttributeEquals('was set anyway', 'unknownProperty', $this->dummyObject);
-    }
-
     /**
      * @test
      */
diff --git a/typo3/sysext/extbase/Tests/Unit/Validation/Validator/CollectionValidatorTest.php b/typo3/sysext/extbase/Tests/Unit/Validation/Validator/CollectionValidatorTest.php
index fd3da88626451fb1b3ade630e0172d347e698154..9c1a38ca7c9d9251c21da2e4640e573e4ea7afa5 100644
--- a/typo3/sysext/extbase/Tests/Unit/Validation/Validator/CollectionValidatorTest.php
+++ b/typo3/sysext/extbase/Tests/Unit/Validation/Validator/CollectionValidatorTest.php
@@ -166,7 +166,6 @@ class CollectionValidatorTest extends UnitTestCase
             'someProperty',
             ['someNotEmptyValue']
         );
-        \TYPO3\CMS\Extbase\Reflection\ObjectAccess::setProperty($lazyObjectStorage, 'isInitialized', false, true);
         // only in this test case we want to mock the isValid method
         $validator = $this->getValidator(['elementType' => $elementType], ['isValid']);
         $validator->expects($this->never())->method('isValid');
diff --git a/typo3/sysext/extbase/Tests/Unit/Validation/Validator/GenericObjectValidatorTest.php b/typo3/sysext/extbase/Tests/Unit/Validation/Validator/GenericObjectValidatorTest.php
index 6b39c92b8943917bf98f78f92f7b1e24f0cb1c8d..c76e450e97a1a0bf06fd31e15cb7939b5c39ea36 100644
--- a/typo3/sysext/extbase/Tests/Unit/Validation/Validator/GenericObjectValidatorTest.php
+++ b/typo3/sysext/extbase/Tests/Unit/Validation/Validator/GenericObjectValidatorTest.php
@@ -58,6 +58,16 @@ class GenericObjectValidatorTest extends UnitTestCase
         $objectWithPrivateProperties = new class() {
             protected $foo = 'foovalue';
             protected $bar = 'barvalue';
+
+            public function getFoo()
+            {
+                return $this->foo;
+            }
+
+            public function getBar()
+            {
+                return $this->bar;
+            }
         };
 
         return [
diff --git a/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/ObjectAccessTest.php b/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/ObjectAccessTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..948257d04fc860567b2c3115deea06ae403dd552
--- /dev/null
+++ b/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/ObjectAccessTest.php
@@ -0,0 +1,95 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\UnitDeprecated\Reflection;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+use TYPO3\CMS\Extbase\Reflection\Exception\PropertyNotAccessibleException;
+use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
+use TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture\DummyClassWithGettersAndSetters;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+/**
+ * Test case
+ */
+class ObjectAccessTest extends UnitTestCase
+{
+    /**
+     * @var bool Reset singletons created by subject
+     */
+    protected $resetSingletonInstances = true;
+
+    /**
+     * @var DummyClassWithGettersAndSetters
+     */
+    protected $dummyObject;
+
+    /**
+     * Set up
+     */
+    protected function setUp()
+    {
+        $this->dummyObject = new DummyClassWithGettersAndSetters();
+        $this->dummyObject->setProperty('string1');
+        $this->dummyObject->setAnotherProperty(42);
+        $this->dummyObject->shouldNotBePickedUp = true;
+    }
+
+    /**
+     * @test
+     */
+    public function getPropertyReturnsExpectedValueForUnexposedPropertyIfForceDirectAccessIsTrue()
+    {
+        $property = ObjectAccess::getProperty($this->dummyObject, 'unexposedProperty', true);
+        $this->assertEquals($property, 'unexposed', 'A property of a given object was not returned correctly.');
+    }
+
+    /**
+     * @test
+     */
+    public function getPropertyReturnsExpectedValueForUnknownPropertyIfForceDirectAccessIsTrue()
+    {
+        $this->dummyObject->unknownProperty = 'unknown';
+        $property = ObjectAccess::getProperty($this->dummyObject, 'unknownProperty', true);
+        $this->assertEquals($property, 'unknown', 'A property of a given object was not returned correctly.');
+    }
+
+    /**
+     * @test
+     */
+    public function getPropertyThrowsPropertyNotAccessibleExceptionForNotExistingPropertyIfForceDirectAccessIsTrue()
+    {
+        $this->expectException(PropertyNotAccessibleException::class);
+        $this->expectExceptionCode(1302855001);
+        ObjectAccess::getProperty($this->dummyObject, 'notExistingProperty', true);
+    }
+
+    /**
+     * @test
+     */
+    public function setPropertySetsValueIfPropertyIsNotAccessibleWhenForceDirectAccessIsTrue()
+    {
+        $this->assertTrue(ObjectAccess::setProperty($this->dummyObject, 'unexposedProperty', 'was set anyway', true));
+        $this->assertAttributeEquals('was set anyway', 'unexposedProperty', $this->dummyObject);
+    }
+
+    /**
+     * @test
+     */
+    public function setPropertySetsValueIfPropertyDoesNotExistWhenForceDirectAccessIsTrue()
+    {
+        $this->assertTrue(ObjectAccess::setProperty($this->dummyObject, 'unknownProperty', 'was set anyway', true));
+        $this->assertAttributeEquals('was set anyway', 'unknownProperty', $this->dummyObject);
+    }
+}
diff --git a/typo3/sysext/extbase/composer.json b/typo3/sysext/extbase/composer.json
index c00971ef457a69a97a716757116743512728b1e6..c8a7842b2a780bbf6a2d7766fd4657ae7b5bdf57 100644
--- a/typo3/sysext/extbase/composer.json
+++ b/typo3/sysext/extbase/composer.json
@@ -14,6 +14,7 @@
 	},
 	"require": {
 		"phpdocumentor/reflection-docblock": "^4.3",
+		"symfony/property-access": "^4.2",
 		"symfony/property-info": "^4.2",
 		"typo3/cms-core": "10.0.*@dev",
 		"webmozart/assert": "^1.0"
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodArgumentDroppedStaticMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodArgumentDroppedStaticMatcher.php
index 79533650c2de356d128aab583b0b4d31344e7a49..950dffcce78c93c5efccc8160bd00809b97a0a3c 100644
--- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodArgumentDroppedStaticMatcher.php
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodArgumentDroppedStaticMatcher.php
@@ -47,4 +47,22 @@ return [
             'Deprecation-85801-GeneralUtilityexplodeUrl2Array-2ndMethodArgument.rst',
         ],
     ],
+    'TYPO3\CMS\Extbase\Reflection\ObjectAccess::getProperty' => [
+        'maximumNumberOfArguments' => 2,
+        'restFiles' => [
+            'Deprecation-87332-AvoidRuntimeReflectionCallsInObjectAccess.rst',
+        ],
+    ],
+    'TYPO3\CMS\Extbase\Reflection\ObjectAccess::getPropertyInternal' => [
+        'maximumNumberOfArguments' => 2,
+        'restFiles' => [
+            'Deprecation-87332-AvoidRuntimeReflectionCallsInObjectAccess.rst',
+        ],
+    ],
+    'TYPO3\CMS\Extbase\Reflection\ObjectAccess::setProperty' => [
+        'maximumNumberOfArguments' => 3,
+        'restFiles' => [
+            'Deprecation-87332-AvoidRuntimeReflectionCallsInObjectAccess.rst',
+        ],
+    ],
 ];
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
index fb54759300bd6d53d3a110412c3a1f7900e3de73..5c98003cc7493a926deadad57449a241758fb9da 100644
--- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
@@ -854,4 +854,11 @@ return [
             'Deprecation-87550-UseControllerClassesWhenRegisteringPluginsmodules.rst',
         ],
     ],
+    'TYPO3\CMS\Extbase\Reflection\ObjectAccess::buildSetterMethodName' => [
+        'numberOfMandatoryArguments' => 1,
+        'maximumNumberOfArguments' => 1,
+        'restFiles' => [
+            'Deprecation-87332-AvoidRuntimeReflectionCallsInObjectAccess.rst',
+        ],
+    ],
 ];