diff --git a/typo3/sysext/backend/Classes/Middleware/BackendRouteInitialization.php b/typo3/sysext/backend/Classes/Middleware/BackendRouteInitialization.php
index 46be9a793f8b5d40d50b9a9dd85c2e24fb780fd3..5ffb1c0ddf2818a82024560aaa3aea53369cefda 100644
--- a/typo3/sysext/backend/Classes/Middleware/BackendRouteInitialization.php
+++ b/typo3/sysext/backend/Classes/Middleware/BackendRouteInitialization.php
@@ -22,8 +22,10 @@ use Psr\Http\Server\RequestHandlerInterface;
 use TYPO3\CMS\Core\Core\Bootstrap;
 
 /**
- * Initializes the Backend Router and also loads ext_tables.php from all extensions, as this is the place
- * where all modules register their routes to the router afterwards.
+ * Loads ext_tables.php from all extensions, as this is the place
+ * where all modules register their routes to the router
+ * (additionally to those routes which are loaded in dependency
+ * inejction factories from Configuration/Backend/{,Ajax}Routes.php).
  *
  * The route path is added to the request as attribute "routePath".
  *
@@ -43,7 +45,8 @@ class BackendRouteInitialization implements MiddlewareInterface
         // Allow the login page to be displayed if routing is not used and on index.php
         $pathToRoute = $request->getQueryParams()['route'] ?? $request->getParsedBody()['route'] ?? '/login';
 
-        Bootstrap::initializeBackendRouter();
+        // Backend Routes from Configuration/Backend/{,Ajax}Routes.php will be implicitly loaded thanks to DI.
+        // Load ext_tables.php files to add routes from ExtensionManagementUtility::addModule() calls.
         Bootstrap::loadExtTables();
 
         // Add the route path to the request
diff --git a/typo3/sysext/backend/Classes/ServiceProvider.php b/typo3/sysext/backend/Classes/ServiceProvider.php
index 3b248f6cc4e04aa9df1f7ab05d45075c12676365..ffeb559a097ce87f11c440418716e33c5c7cae02 100644
--- a/typo3/sysext/backend/Classes/ServiceProvider.php
+++ b/typo3/sysext/backend/Classes/ServiceProvider.php
@@ -20,9 +20,11 @@ use Psr\Container\ContainerInterface;
 use TYPO3\CMS\Core\Cache\Exception\InvalidDataException;
 use TYPO3\CMS\Core\Configuration\ConfigurationManager;
 use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Exception as CoreException;
 use TYPO3\CMS\Core\Http\MiddlewareDispatcher;
 use TYPO3\CMS\Core\Http\MiddlewareStackResolver;
+use TYPO3\CMS\Core\Information\Typo3Version;
 use TYPO3\CMS\Core\Package\AbstractServiceProvider;
 
 /**
@@ -42,9 +44,17 @@ class ServiceProvider extends AbstractServiceProvider
             Http\RequestHandler::class => [ static::class, 'getRequestHandler' ],
             Http\RouteDispatcher::class => [ static::class, 'getRouteDispatcher' ],
             'backend.middlewares' => [ static::class, 'getBackendMiddlewares' ],
+            'backend.routes' => [ static::class, 'getBackendRoutes' ],
         ];
     }
 
+    public function getExtensions(): array
+    {
+        return [
+            Routing\Router::class => [ static::class, 'configureBackendRouter' ],
+        ] + parent::getExtensions();
+    }
+
     public static function getApplication(ContainerInterface $container): Http\Application
     {
         $requestHandler = new MiddlewareDispatcher(
@@ -79,4 +89,32 @@ class ServiceProvider extends AbstractServiceProvider
     {
         return new ArrayObject($container->get(MiddlewareStackResolver::class)->resolve('backend'));
     }
+
+    public static function configureBackendRouter(ContainerInterface $container, Routing\Router $router = null): Routing\Router
+    {
+        $router = $router ?? self::new($container, Routing\Router::class);
+        $cache = $container->get('cache.core');
+
+        $cacheIdentifier = 'BackendRoutes_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath() . 'BackendRoutes');
+        if ($cache->has($cacheIdentifier)) {
+            $routesFromPackages = $cache->require($cacheIdentifier);
+        } else {
+            $routesFromPackages = $container->get('backend.routes')->getArrayCopy();
+            $cache->set($cacheIdentifier, 'return ' . var_export($routesFromPackages, true) . ';');
+        }
+
+        foreach ($routesFromPackages as $name => $options) {
+            $path = $options['path'];
+            unset($options['path']);
+            $route = new Routing\Route($path, $options);
+            $router->addRoute($name, $route);
+        }
+
+        return $router;
+    }
+
+    public static function getBackendRoutes(ContainerInterface $container): ArrayObject
+    {
+        return new ArrayObject();
+    }
 }
diff --git a/typo3/sysext/core/Classes/Console/CommandApplication.php b/typo3/sysext/core/Classes/Console/CommandApplication.php
index c31654bb8399a8d0cca4d14724070f023843f228..3492572060ba3929aaa9ca44371bfdab0b0f5f9c 100644
--- a/typo3/sysext/core/Classes/Console/CommandApplication.php
+++ b/typo3/sysext/core/Classes/Console/CommandApplication.php
@@ -72,7 +72,6 @@ class CommandApplication implements ApplicationInterface
         $input = new ArgvInput();
         $output = new ConsoleOutput();
 
-        Bootstrap::initializeBackendRouter();
         Bootstrap::loadExtTables();
         // create the BE_USER object (not logged in yet)
         Bootstrap::initializeBackendUser(CommandLineUserAuthentication::class);
diff --git a/typo3/sysext/core/Classes/Core/Bootstrap.php b/typo3/sysext/core/Classes/Core/Bootstrap.php
index 4983df57ba3e932d272d5ad2aa12842e9ee3eaa9..4d9cf9cff5749baecfdd8e95c3b0d717921cde76 100644
--- a/typo3/sysext/core/Classes/Core/Bootstrap.php
+++ b/typo3/sysext/core/Classes/Core/Bootstrap.php
@@ -30,7 +30,6 @@ use TYPO3\CMS\Core\Cache\Frontend\VariableFrontend;
 use TYPO3\CMS\Core\Configuration\ConfigurationManager;
 use TYPO3\CMS\Core\DependencyInjection\ContainerBuilder;
 use TYPO3\CMS\Core\Imaging\IconRegistry;
-use TYPO3\CMS\Core\Information\Typo3Version;
 use TYPO3\CMS\Core\IO\PharStreamWrapperInterceptor;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Log\LogManager;
@@ -538,55 +537,12 @@ class Bootstrap
      * Loads all routes registered inside all packages and stores them inside the Router
      *
      * @internal This is not a public API method, do not use in own extensions
+     * @deprecated this does not do anything anymore, as TYPO3's dependency injection already loads the routes on demand.
      */
     public static function initializeBackendRouter()
     {
-        // See if the Routes.php from all active packages have been built together already
-        $cacheIdentifier = 'BackendRoutesFromPackages_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath() . 'BackendRoutesFromPackages');
-
-        /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $codeCache */
-        $codeCache = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class)->getCache('core');
-        $routesFromPackages = [];
-        if ($codeCache->has($cacheIdentifier)) {
-            // substr is necessary, because the php frontend wraps php code around the cache value
-            $routesFromPackages = unserialize(substr($codeCache->get($cacheIdentifier), 6, -2));
-        } else {
-            // Loop over all packages and check for a Configuration/Backend/Routes.php file
-            $packageManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Package\PackageManager::class);
-            $packages = $packageManager->getActivePackages();
-            foreach ($packages as $package) {
-                $routesFileNameForPackage = $package->getPackagePath() . 'Configuration/Backend/Routes.php';
-                if (file_exists($routesFileNameForPackage)) {
-                    $definedRoutesInPackage = require $routesFileNameForPackage;
-                    if (is_array($definedRoutesInPackage)) {
-                        $routesFromPackages = array_merge($routesFromPackages, $definedRoutesInPackage);
-                    }
-                }
-                $routesFileNameForPackage = $package->getPackagePath() . 'Configuration/Backend/AjaxRoutes.php';
-                if (file_exists($routesFileNameForPackage)) {
-                    $definedRoutesInPackage = require $routesFileNameForPackage;
-                    if (is_array($definedRoutesInPackage)) {
-                        foreach ($definedRoutesInPackage as $routeIdentifier => $routeOptions) {
-                            // prefix the route with "ajax_" as "namespace"
-                            $routeOptions['path'] = '/ajax' . $routeOptions['path'];
-                            $routesFromPackages['ajax_' . $routeIdentifier] = $routeOptions;
-                            $routesFromPackages['ajax_' . $routeIdentifier]['ajax'] = true;
-                        }
-                    }
-                }
-            }
-            // Store the data from all packages in the cache
-            $codeCache->set($cacheIdentifier, serialize($routesFromPackages));
-        }
-
-        // Build Route objects from the data
-        $router = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\Router::class);
-        foreach ($routesFromPackages as $name => $options) {
-            $path = $options['path'];
-            unset($options['path']);
-            $route = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\Route::class, $path, $options);
-            $router->addRoute($name, $route);
-        }
+        // TODO: Once typo3/testing-framework is adapted, this code can be dropped / deprecated. As DI is already
+        // loading all routes on demand, this method is not needed anymore.
     }
 
     /**
diff --git a/typo3/sysext/core/Classes/DependencyInjection/ServiceProviderCompilationPass.php b/typo3/sysext/core/Classes/DependencyInjection/ServiceProviderCompilationPass.php
index 9e311289c2abe0015162af664bfe6bbf2ed8bf53..9d4a3c82127d41072d7b162481b36f99f3a78a31 100644
--- a/typo3/sysext/core/Classes/DependencyInjection/ServiceProviderCompilationPass.php
+++ b/typo3/sysext/core/Classes/DependencyInjection/ServiceProviderCompilationPass.php
@@ -128,7 +128,7 @@ class ServiceProviderCompilationPass implements CompilerPassInterface
             }
         }
 
-        $className = $this->getReturnType($this->getReflection($callable), $serviceName);
+        $className = $this->getReturnType($this->getReflection($callable), $serviceName) ?? 'object';
         $factoryDefinition->setClass($className);
         $factoryDefinition->setPublic(true);
 
@@ -160,7 +160,8 @@ class ServiceProviderCompilationPass implements CompilerPassInterface
         $innerName = null;
 
         $reflection = $this->getReflection($callable);
-        $className = $this->getReturnType($reflection, $serviceName);
+        $previousClass = $container->has($serviceName) ? $container->findDefinition($serviceName)->getClass() : null;
+        $className = $this->getReturnType($reflection, $serviceName) ?? $previousClass ?? 'object';
 
         $factoryDefinition = new Definition($className);
         $factoryDefinition->setClass($className);
@@ -216,15 +217,19 @@ class ServiceProviderCompilationPass implements CompilerPassInterface
     /**
      * @param \ReflectionFunctionAbstract $reflection
      * @param string $serviceName
-     * @return string
+     * @return string|null
      */
-    private function getReturnType(\ReflectionFunctionAbstract $reflection, string $serviceName): string
+    private function getReturnType(\ReflectionFunctionAbstract $reflection, string $serviceName): ?string
     {
         if ($reflection->getReturnType() instanceof \ReflectionNamedType) {
             return $reflection->getReturnType()->getName();
         }
 
-        return $serviceName;
+        if (class_exists($serviceName, true) || interface_exists($serviceName, true)) {
+            return $serviceName;
+        }
+
+        return null;
     }
 
     /**
diff --git a/typo3/sysext/core/Classes/Package/AbstractServiceProvider.php b/typo3/sysext/core/Classes/Package/AbstractServiceProvider.php
index 8753e8fe95e97c7cd4395f54ab3c9a38b186b731..ef5dbd06a2472d33db852c8cb30521ac177309b8 100644
--- a/typo3/sysext/core/Classes/Package/AbstractServiceProvider.php
+++ b/typo3/sysext/core/Classes/Package/AbstractServiceProvider.php
@@ -48,6 +48,7 @@ abstract class AbstractServiceProvider implements ServiceProviderInterface
     {
         return [
             'middlewares' => [ static::class, 'configureMiddlewares' ],
+            'backend.routes' => [ static::class, 'configureBackendRoutes' ],
         ];
     }
 
@@ -70,6 +71,38 @@ abstract class AbstractServiceProvider implements ServiceProviderInterface
         return $middlewares;
     }
 
+    /**
+     * @param ContainerInterface $container
+     * @param ArrayObject $middlewares
+     * @param string $path supplied when invoked internally through PseudoServiceProvider
+     * @return ArrayObject
+     */
+    public static function configureBackendRoutes(ContainerInterface $container, ArrayObject $routes, string $path = null): ArrayObject
+    {
+        $path = $path ?? static::getPackagePath();
+        $routesFileNameForPackage = $path . 'Configuration/Backend/Routes.php';
+        if (file_exists($routesFileNameForPackage)) {
+            $definedRoutesInPackage = require $routesFileNameForPackage;
+            if (is_array($definedRoutesInPackage)) {
+                $routes->exchangeArray(array_merge($routes->getArrayCopy(), $definedRoutesInPackage));
+            }
+        }
+        $routesFileNameForPackage = $path . 'Configuration/Backend/AjaxRoutes.php';
+        if (file_exists($routesFileNameForPackage)) {
+            $definedRoutesInPackage = require $routesFileNameForPackage;
+            if (is_array($definedRoutesInPackage)) {
+                foreach ($definedRoutesInPackage as $routeIdentifier => $routeOptions) {
+                    // prefix the route with "ajax_" as "namespace"
+                    $routeOptions['path'] = '/ajax' . $routeOptions['path'];
+                    $routes['ajax_' . $routeIdentifier] = $routeOptions;
+                    $routes['ajax_' . $routeIdentifier]['ajax'] = true;
+                }
+            }
+        }
+
+        return $routes;
+    }
+
     /**
      * Create an instance of a class. Supports auto injection of the logger.
      *
diff --git a/typo3/sysext/frontend/Classes/Middleware/BackendUserAuthenticator.php b/typo3/sysext/frontend/Classes/Middleware/BackendUserAuthenticator.php
index f592bcd7c63771f770675602b06446bebc0738bf..23ae94503f85e5f9889dbdc34d98773a07fbd9af 100644
--- a/typo3/sysext/frontend/Classes/Middleware/BackendUserAuthenticator.php
+++ b/typo3/sysext/frontend/Classes/Middleware/BackendUserAuthenticator.php
@@ -61,7 +61,6 @@ class BackendUserAuthenticator extends \TYPO3\CMS\Core\Middleware\BackendUserAut
             $GLOBALS['BE_USER']->setWorkspace($GLOBALS['BE_USER']->user['workspace_id']);
             $this->setBackendUserAspect($GLOBALS['BE_USER']);
             $GLOBALS['LANG'] = LanguageService::createFromUserPreferences($GLOBALS['BE_USER']);
-            Bootstrap::initializeBackendRouter();
             Bootstrap::loadExtTables();
         }