diff --git a/core/modules/jsonapi/src/EventSubscriber/DefaultExceptionSubscriber.php b/core/modules/jsonapi/src/EventSubscriber/DefaultExceptionSubscriber.php
index 2967ddc67b..22dcf0869f 100644
--- a/core/modules/jsonapi/src/EventSubscriber/DefaultExceptionSubscriber.php
+++ b/core/modules/jsonapi/src/EventSubscriber/DefaultExceptionSubscriber.php
@@ -12,6 +12,8 @@
 use Drupal\serialization\EventSubscriber\DefaultExceptionSubscriber as SerializationDefaultExceptionSubscriber;
 use Symfony\Component\HttpKernel\Event\ExceptionEvent;
 use Symfony\Component\HttpKernel\Exception\HttpException;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
 
 /**
  * Serializes exceptions in compliance with the  JSON:API specification.
@@ -74,7 +76,8 @@ protected function setEventResponse(ExceptionEvent $event, $status) {
    * Check if the error should be formatted using JSON:API.
    *
    * The JSON:API format is supported if the format is explicitly set or the
-   * request is for a known JSON:API route.
+   * request is for a known JSON:API route. Because route filters must be run
+   * for the format to be set, this function will attempt a match request.
    *
    * @param \Symfony\Component\HttpKernel\Event\ExceptionEvent $exception_event
    *   The exception event.
@@ -84,8 +87,48 @@ protected function setEventResponse(ExceptionEvent $event, $status) {
    */
   protected function isJsonApiExceptionEvent(ExceptionEvent $exception_event) {
     $request = $exception_event->getRequest();
+
+    $request_format = $request->getRequestFormat(NULL);
+    if (is_null($request_format)) {
+      try {
+        /** @var \Drupal\Core\Routing\RouteProvider $route_provider */
+        $route_provider = \Drupal::service('router.route_provider');
+        $collection = $route_provider->getRouteCollectionForRequest($request);
+
+        $possible_formats = static::getAvailableFormats($collection);
+        if ($possible_formats === ['api_json']) {
+          $request_format = 'api_json';
+        }
+      }
+      catch (\Exception $e) {
+        return FALSE;
+      }
+    }
+
     $parameters = $request->attributes->all();
-    return $request->getRequestFormat() === 'api_json' || (bool) Routes::getResourceTypeNameFromParameters($parameters);
+    return $request_format === 'api_json' || (bool) Routes::getResourceTypeNameFromParameters($parameters);
+  }
+
+  /**
+   * Gets the set of formats across all routes in the collection.
+   *
+   * @param \Symfony\Component\Routing\RouteCollection $collection
+   *   The route collection to filter.
+   *
+   * @return string[]
+   *   All available formats.
+   *
+   * @see \Drupal\Core\Routing\RequestFormatRouteFilter::getAvailableFormats
+   */
+  protected static function getAvailableFormats(RouteCollection $collection) {
+    $all_formats = array_reduce($collection->all(), function (array $carry, Route $route) {
+      // Routes without a '_format' requirement are assumed to require HTML.
+      $route_formats = !$route->hasRequirement('_format')
+        ? ['html']
+        : explode('|', $route->getRequirement('_format'));
+      return array_merge($carry, $route_formats);
+    }, []);
+    return array_unique(array_filter($all_formats));
   }
 
 }
diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_auth_exception/jsonapi_test_auth_exception.info.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_auth_exception/jsonapi_test_auth_exception.info.yml
new file mode 100644
index 0000000000..996d17b6ea
--- /dev/null
+++ b/core/modules/jsonapi/tests/modules/jsonapi_test_auth_exception/jsonapi_test_auth_exception.info.yml
@@ -0,0 +1,4 @@
+name: 'JSON API test auth exception'
+type: module
+description: 'Provides an authorization exception provider to test JSON:API authorization.'
+package: Testing
diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_auth_exception/jsonapi_test_auth_exception.services.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_auth_exception/jsonapi_test_auth_exception.services.yml
new file mode 100644
index 0000000000..13ed2e7e74
--- /dev/null
+++ b/core/modules/jsonapi/tests/modules/jsonapi_test_auth_exception/jsonapi_test_auth_exception.services.yml
@@ -0,0 +1,6 @@
+services:
+  jsonapi_test_auth_exception.authentication.simple_oauth:
+    class: Drupal\jsonapi_test_auth_exception\Authentication\Provider\AuthenticationWithExceptionProvider
+    arguments: {}
+    tags:
+      - { name: authentication_provider, provider_id: jsonapi_test_auth_exception, global: TRUE, priority: 35 }
diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_auth_exception/src/Authentication/Provider/AuthenticationWithExceptionProvider.php b/core/modules/jsonapi/tests/modules/jsonapi_test_auth_exception/src/Authentication/Provider/AuthenticationWithExceptionProvider.php
new file mode 100644
index 0000000000..ab56c23d16
--- /dev/null
+++ b/core/modules/jsonapi/tests/modules/jsonapi_test_auth_exception/src/Authentication/Provider/AuthenticationWithExceptionProvider.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\jsonapi_test_auth_exception\Authentication\Provider;
+
+use Drupal\Core\Authentication\AuthenticationProviderInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\HttpException;
+
+/**
+ * An authentication provider that throws an auth exception when requested.
+ *
+ * To request an auth exception pass a "Authorization: Bearer exception"
+ * header.
+ */
+class AuthenticationWithExceptionProvider implements AuthenticationProviderInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applies(Request $request) {
+    if (!$request->headers->has('Authorization')) {
+      return FALSE;
+    }
+    if ('Bearer exception' == $request->headers->get('Authorization')) {
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function authenticate(Request $request) {
+    throw new HttpException(
+      401,
+      'An auth exception was requested.'
+    );
+  }
+
+}
diff --git a/core/modules/jsonapi/tests/src/Functional/RestJsonApiAuthException.php b/core/modules/jsonapi/tests/src/Functional/RestJsonApiAuthException.php
new file mode 100644
index 0000000000..dd3e28e554
--- /dev/null
+++ b/core/modules/jsonapi/tests/src/Functional/RestJsonApiAuthException.php
@@ -0,0 +1,130 @@
+<?php
+
+namespace Drupal\Tests\jsonapi\Functional;
+
+use Drupal\Core\Url;
+use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
+use Drupal\Tests\rest\Functional\ResourceTestBase;
+use GuzzleHttp\RequestOptions;
+
+/**
+ * Ensures that the 'api_json' format is used even on auth exception.
+ *
+ * @group jsonapi
+ *
+ * @internal
+ */
+class RestJsonApiAuthException extends ResourceTestBase {
+
+  use AnonResourceTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'jsonapi',
+    'node',
+    'user',
+    'jsonapi_test_user',
+    'jsonapi_test_auth_exception',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $format = 'api_json';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $mimeType = 'application/vnd.api+json';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $resourceConfigId = 'entity.node';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUpAuthorization($method) {
+    // @todo Remove this in
+    $this->grantPermissionsToTestedRole(['access content']);
+
+    switch ($method) {
+      case 'GET':
+        $this->grantPermissionsToTestedRole(['access user profiles']);
+        break;
+
+      case 'POST':
+      case 'PATCH':
+      case 'DELETE':
+        $this->grantPermissionsToTestedRole(['administer users']);
+        break;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp(): void {
+    parent::setUp();
+
+    // Set up a HTTP client that accepts relative URLs.
+    $this->httpClient = $this->container->get('http_client_factory')
+      ->fromOptions(['base_uri' => $this->baseUrl]);
+  }
+
+  /**
+   * An auth exception to a JSON API route results in a JSON API response.
+   *
+   * @see \Drupal\jsonapi\EventSubscriber\JsonApiRequestValidator::validateQueryParams()
+   */
+  public function testApiJsonAuthExceptionIsJson() {
+    $user = $this->setUpCurrentUser();
+    $this->assertSame(['json', 'xml'], $this->container->getParameter('serializer.formats'));
+
+    $this->provisionResource(['api_json'], []);
+    $this->setUpAuthorization('GET');
+
+    $url = Url::fromRoute(sprintf('jsonapi.user--user.individual'), ['entity' => $user->uuid()]);
+    $request_options = [
+      RequestOptions::HEADERS => [
+        'Authorization' => 'Bearer exception',
+        'Cache-Control' => 'no-cache',
+      ],
+    ];
+    $response = $this->request('GET', $url, $request_options);
+
+    $this->assertResourceErrorResponse(
+      401,
+      FALSE,
+      $response,
+      ['4xx-response', 'http_response'],
+      ['url.site'],
+      'MISS',
+      'UNCACHEABLE'
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function assertNormalizationEdgeCases($method, Url $url, array $request_options) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getExpectedUnauthorizedAccessMessage($method) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getExpectedUnauthorizedAccessCacheability() {}
+
+}
