diff --git a/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php b/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php
index 28b0643531421dd4119f83ea145cbb5d53740934..d78478cb9763730c2582b8df00d6a5f5eda19c03 100644
--- a/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php
+++ b/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php
@@ -22,7 +22,9 @@ use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
 use TYPO3\CMS\Extbase\Event\Mvc\BeforeActionCallEvent;
 use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
+use TYPO3\CMS\Extbase\Mvc\View\GenericViewResolver;
 use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
+use TYPO3\CMS\Extbase\Mvc\View\ViewResolverInterface;
 use TYPO3\CMS\Extbase\Mvc\Web\ReferringRequest;
 use TYPO3\CMS\Extbase\Security\Cryptography\HashService;
 use TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator;
@@ -49,6 +51,11 @@ class ActionController implements ControllerInterface
      */
     protected $hashService;
 
+    /**
+     * @var ViewResolverInterface
+     */
+    private $viewResolver;
+
     /**
      * The current view, as resolved by resolveView()
      *
@@ -102,6 +109,15 @@ class ActionController implements ControllerInterface
      */
     protected $response;
 
+    /**
+     * @param ViewResolverInterface $viewResolver
+     * @internal
+     */
+    public function injectViewResolver(ViewResolverInterface $viewResolver)
+    {
+        $this->viewResolver = $viewResolver;
+    }
+
     /**
      * @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
      */
@@ -370,10 +386,22 @@ class ActionController implements ControllerInterface
      */
     protected function resolveView()
     {
-        $view = null;
-        if ($this->defaultViewObjectName != '') {
-            /** @var ViewInterface $view */
-            $view = $this->objectManager->get($this->defaultViewObjectName);
+        if ($this->viewResolver instanceof GenericViewResolver) {
+            /*
+             * This setter is not part of the ViewResolverInterface as it's only necessary to set
+             * the default view class from this point when using the generic view resolver which
+             * must respect the possibly overridden property defaultViewObjectName.
+             */
+            $this->viewResolver->setDefaultViewClass($this->defaultViewObjectName);
+        }
+
+        $view = $this->viewResolver->resolve(
+            $this->request->getControllerObjectName(),
+            $this->request->getControllerActionName(),
+            $this->request->getFormat()
+        );
+
+        if ($view instanceof ViewInterface) {
             $this->setViewConfiguration($view);
             if ($view->canRender($this->controllerContext) === false) {
                 $view = null;
diff --git a/typo3/sysext/extbase/Classes/Mvc/View/GenericViewResolver.php b/typo3/sysext/extbase/Classes/Mvc/View/GenericViewResolver.php
new file mode 100644
index 0000000000000000000000000000000000000000..599d9a27a8f165a7986df3e9b124d577a7cfc50a
--- /dev/null
+++ b/typo3/sysext/extbase/Classes/Mvc/View/GenericViewResolver.php
@@ -0,0 +1,54 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Mvc\View;
+
+/*
+ * 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\Object\ObjectManager;
+
+/**
+ * @internal only to be used within Extbase, not part of TYPO3 Core API.
+ */
+class GenericViewResolver implements ViewResolverInterface
+{
+    /**
+     * @var ObjectManager
+     */
+    private $objectManager;
+
+    /**
+     * @var string
+     */
+    private $defaultViewClass;
+
+    public function __construct(ObjectManager $objectManager)
+    {
+        $this->objectManager = $objectManager;
+    }
+
+    /**
+     * @param string $defaultViewClass
+     * @internal
+     */
+    public function setDefaultViewClass(string $defaultViewClass): void
+    {
+        $this->defaultViewClass = $defaultViewClass;
+    }
+
+    public function resolve(string $controllerObjectName, string $actionName, string $format): ViewInterface
+    {
+        return $this->objectManager->get($format === 'json' ? JsonView::class : $this->defaultViewClass);
+    }
+}
diff --git a/typo3/sysext/extbase/Classes/Mvc/View/ViewResolverInterface.php b/typo3/sysext/extbase/Classes/Mvc/View/ViewResolverInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..06a7c127a7b5dceb7cd508d04fb5caf05e0da3bc
--- /dev/null
+++ b/typo3/sysext/extbase/Classes/Mvc/View/ViewResolverInterface.php
@@ -0,0 +1,15 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Mvc\View;
+
+/**
+ * @internal only to be used within Extbase, not part of TYPO3 Core API.
+ *
+ * It's safe to use this interface in TYPO3 10 LTS as it will not be changed or removed in that
+ * version but this interface is likely to be removed and/or changed in version 11.
+ */
+interface ViewResolverInterface
+{
+    public function resolve(string $controllerObjectName, string $actionName, string $format): ViewInterface;
+}
diff --git a/typo3/sysext/extbase/Configuration/Services.yaml b/typo3/sysext/extbase/Configuration/Services.yaml
index e393761fbb7b76e199c62248e54bb51ecee8f4e8..d0c5dfda66bc86bb9ed68ad9261fc82114d16a82 100644
--- a/typo3/sysext/extbase/Configuration/Services.yaml
+++ b/typo3/sysext/extbase/Configuration/Services.yaml
@@ -83,3 +83,6 @@ services:
         identifier: 'legacy-slot'
         method: 'emitAfterPersistObjectSignal'
         event: TYPO3\CMS\Extbase\Event\Persistence\EntityPersistedEvent
+
+  TYPO3\CMS\Extbase\Mvc\View\GenericViewResolver: ~
+  TYPO3\CMS\Extbase\Mvc\View\ViewResolverInterface: '@TYPO3\CMS\Extbase\Mvc\View\GenericViewResolver'
diff --git a/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/ActionControllerTest.php b/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/ActionControllerTest.php
index 34c0f0af74e7f54c0d27574925efba69f508a1b1..660571afe672322bd0288ca3f20af9c55627510d 100644
--- a/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/ActionControllerTest.php
+++ b/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/ActionControllerTest.php
@@ -16,6 +16,7 @@ namespace TYPO3\CMS\Extbase\Tests\Functional\Mvc\Controller;
  */
 
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Mvc\View\JsonView;
 use TYPO3\CMS\Extbase\Mvc\Web\Request;
 use TYPO3\CMS\Extbase\Mvc\Web\Response;
 use TYPO3\CMS\Extbase\Object\ObjectManager;
@@ -116,4 +117,50 @@ class ActionControllerTest extends \TYPO3\TestingFramework\Core\Functional\Funct
         $validators->rewind();
         self::assertInstanceOf(NotEmptyValidator::class, $validators->current());
     }
+
+    /**
+     * @test
+     */
+    public function resolveViewRespectsRequestedFormat()
+    {
+        // Test setup
+        $this->request->setControllerActionName('qux');
+        $this->request->setFormat('json');
+
+        // Test run
+        $this->controller->processRequest($this->request, $this->response);
+
+        // Assertions
+        $reflectionClass = new \ReflectionClass($this->controller);
+        $reflectionMethod = $reflectionClass->getProperty('view');
+        $reflectionMethod->setAccessible(true);
+
+        $view = $reflectionMethod->getValue($this->controller);
+        self::assertInstanceOf(JsonView::class, $view);
+    }
+
+    /**
+     * @test
+     */
+    public function resolveViewRespectsDefaultViewObjectName()
+    {
+        // Test setup
+        $reflectionClass = new \ReflectionClass($this->controller);
+        $reflectionMethod = $reflectionClass->getProperty('defaultViewObjectName');
+        $reflectionMethod->setAccessible(true);
+        $reflectionMethod->setValue($this->controller, JsonView::class);
+
+        $this->request->setControllerActionName('qux');
+
+        // Test run
+        $this->controller->processRequest($this->request, $this->response);
+
+        // Assertions
+        $reflectionMethod = $reflectionClass->getProperty('view');
+        $reflectionMethod->setAccessible(true);
+        $reflectionMethod->getValue($this->controller);
+
+        $view = $reflectionMethod->getValue($this->controller);
+        self::assertInstanceOf(JsonView::class, $view);
+    }
 }
diff --git a/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Controller/TestController.php b/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Controller/TestController.php
index 52889fe1a9af6861c5d25d8ac5cf0f6afb4eddf4..a936ba4606322508dc0dbb1b962b52df143a8e45 100644
--- a/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Controller/TestController.php
+++ b/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Controller/TestController.php
@@ -69,4 +69,9 @@ class TestController extends ActionController
         // return string so we don't need to mock a view
         return '';
     }
+
+    public function quxAction()
+    {
+        return '';
+    }
 }