diff --git a/README.md b/README.md
index 9ab02e0..c5d7681 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ Unlike the core REST module JSON API doesn't really require any kind of configur
 The jsonapi module exposes both config and content entity resources. On top of that it exposes one resource per bundle per entity. The default format appears like: `/jsonapi/{entity_type}/{bundle}/{uuid}?_format=api_json`
 
 The list of endpoints then looks like the following:
-* `/jsonapi/node/article?_format=api_json`: Exposes a collection of article content
-* `/jsonapi/node/article/{UUID}?_format=api_json`: Exposes an individual article
-* `/jsonapi/block?_format=api_json`: Exposes a collection of blocks
-* `/jsonapi/block/{block}?_format=api_json`: Exposes an individual block
+* `/jsonapi/node/article`: Exposes a collection of article content
+* `/jsonapi/node/article/{UUID}`: Exposes an individual article
+* `/jsonapi/block`: Exposes a collection of blocks
+* `/jsonapi/block/{block}`: Exposes an individual block
diff --git a/jsonapi.services.yml b/jsonapi.services.yml
index 0b10a97..4b65c59 100644
--- a/jsonapi.services.yml
+++ b/jsonapi.services.yml
@@ -87,7 +87,7 @@ services:
   access_check.jsonapi.custom_query_parameter_names:
     class: Drupal\jsonapi\Access\CustomQueryParameterNamesAccessCheck
     tags:
-      - { name: access_check, applies_to: _jsonapi_custom_query_parameter_names }
+      - { name: access_check, applies_to: _jsonapi_custom_query_parameter_names, needs_incoming_request: TRUE }
   paramconverter.jsonapi.entity_uuid:
     class: Drupal\jsonapi\ParamConverter\EntityUuidConverter
     tags:
@@ -99,6 +99,12 @@ services:
     tags:
       - { name: event_subscriber }
     arguments: ['@serializer', '%serializer.formats%']
+  jsonapi.http_middleware.format_setter:
+    class: Drupal\jsonapi\StackMiddleware\FormatSetter
+    tags:
+    # Set priority to 399 so it happens right after the format in the ?_format
+    # has been set in the request. That is set to 400.
+      - { name: http_middleware, priority: 399, responder: true }
 
   logger.channel.jsonapi:
     parent: logger.channel_base
diff --git a/src/Controller/EntryPoint.php b/src/Controller/EntryPoint.php
index 9233ef4..c5a322f 100644
--- a/src/Controller/EntryPoint.php
+++ b/src/Controller/EntryPoint.php
@@ -65,8 +65,7 @@ class EntryPoint extends ControllerBase {
     /** @var \Drupal\Core\Cache\CacheableResponseInterface $response */
     $do_build_urls = function () {
       $self = Url::fromRoute('jsonapi.resource_list')
-        ->setOption('absolute', TRUE)
-        ->setOption('query', ['_format' => 'api_json']);
+        ->setOption('absolute', TRUE);
 
       return array_reduce($this->resourceTypeRepository->all(), function (array $carry, ResourceType $resource_type) {
         // TODO: Learn how to invalidate the cache for this page when a new entity
@@ -74,7 +73,6 @@ class EntryPoint extends ControllerBase {
         // $this->response->addCacheableDependency($definition);
         $url = Url::fromRoute(sprintf('jsonapi.%s.collection', $resource_type->getTypeName()));
         $url->setOption('absolute', TRUE);
-        $url->setOption('query', ['_format' => 'api_json']);
         $carry[$resource_type->getTypeName()] = $url->toString();
 
         return $carry;
diff --git a/src/Controller/RequestHandler.php b/src/Controller/RequestHandler.php
index 42d9319..fcd85fe 100644
--- a/src/Controller/RequestHandler.php
+++ b/src/Controller/RequestHandler.php
@@ -11,6 +11,7 @@ use Symfony\Component\DependencyInjection\ContainerAwareTrait;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\HttpException;
 use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\Serializer\Exception\UnexpectedValueException;
diff --git a/src/EventSubscriber/DefaultExceptionSubscriber.php b/src/EventSubscriber/DefaultExceptionSubscriber.php
index ea87b35..ef57d37 100644
--- a/src/EventSubscriber/DefaultExceptionSubscriber.php
+++ b/src/EventSubscriber/DefaultExceptionSubscriber.php
@@ -3,6 +3,7 @@
 namespace Drupal\jsonapi\EventSubscriber;
 
 use Drupal\serialization\EventSubscriber\DefaultExceptionSubscriber as SerializationDefaultExceptionSubscriber;
+use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
 use Symfony\Component\HttpKernel\Exception\HttpException;
@@ -32,8 +33,7 @@ class DefaultExceptionSubscriber extends SerializationDefaultExceptionSubscriber
   public function onException(GetResponseForExceptionEvent $event) {
     /** @var \Symfony\Component\HttpKernel\Exception\HttpException $exception */
     $exception = $event->getException();
-    $format = $event->getRequest()->getRequestFormat();
-    if ($format !== 'api_json') {
+    if (!$this->isJsonApiFormatted($event->getRequest())) {
       return;
     }
     if (!$exception instanceof HttpException) {
@@ -50,13 +50,29 @@ class DefaultExceptionSubscriber extends SerializationDefaultExceptionSubscriber
   protected function setEventResponse(GetResponseForExceptionEvent $event, $status) {
     /** @var \Symfony\Component\HttpKernel\Exception\HttpException $exception */
     $exception = $event->getException();
-    $format = $event->getRequest()->getRequestFormat();
-    if ($format !== 'api_json') {
+    if (!$this->isJsonApiFormatted($event->getRequest())) {
       return;
     }
-    $encoded_content = $this->serializer->serialize($exception, $format, ['data_wrapper' => 'errors']);
+    $encoded_content = $this->serializer->serialize($exception, 'api_json', ['data_wrapper' => 'errors']);
     $response = new Response($encoded_content, $status);
+    $response->headers->set('content-type', 'application/vnd.api+json');
     $event->setResponse($response);
   }
 
+  /**
+   * Check if the error should be formatted using JSON API.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The failed request.
+   *
+   * @return bool
+   *   TRUE if it needs to be formated using JSON API. FALSE otherwise.
+   */
+  protected function isJsonApiFormatted(Request $request) {
+    $format = $request->getRequestFormat();
+    // The JSON API format is supported if the format is explicitly set or the
+    // request is for a known JSON API route.
+    return $format == 'api_json' || $request->attributes->get('_is_jsonapi');
+  }
+
 }
diff --git a/src/LinkManager/LinkManager.php b/src/LinkManager/LinkManager.php
index 672d801..802e535 100644
--- a/src/LinkManager/LinkManager.php
+++ b/src/LinkManager/LinkManager.php
@@ -57,7 +57,6 @@ class LinkManager {
   public function getEntityLink($entity_id, ResourceType $resource_type, array $route_parameters, $key) {
     $route_parameters += [
       $resource_type->getEntityTypeId() => $entity_id,
-      '_format' => 'api_json',
     ];
     $route_key = sprintf('jsonapi.%s.%s', $resource_type->getTypeName(), $key);
     return $this->urlGenerator->generateFromRoute($route_key, $route_parameters, ['absolute' => TRUE]);
diff --git a/src/Routing/Routes.php b/src/Routing/Routes.php
index aaeb28e..00b49b7 100644
--- a/src/Routing/Routes.php
+++ b/src/Routing/Routes.php
@@ -84,7 +84,6 @@ class Routes implements ContainerInjectionInterface {
       RouteObjectInterface::CONTROLLER_NAME => '\Drupal\jsonapi\Controller\EntryPoint::index',
     ]))
       ->setRequirement('_permission', 'access jsonapi resource list')
-      ->setRequirement('_format', 'api_json')
       ->setMethods(['GET']);
     $route_collection->addOptions([
       '_auth' => $this->authProviderList(),
@@ -120,7 +119,6 @@ class Routes implements ContainerInjectionInterface {
         ->setRequirement('_entity_type', $resource_type->getEntityTypeId())
         ->setRequirement('_bundle', $resource_type->getBundle())
         ->setRequirement('_permission', 'access content')
-        ->setRequirement('_format', 'api_json')
         ->setRequirement('_jsonapi_custom_query_parameter_names', 'TRUE')
         ->setOption('serialization_class', JsonApiDocumentTopLevel::class)
         ->setMethods(['GET', 'POST']);
@@ -134,7 +132,6 @@ class Routes implements ContainerInjectionInterface {
         ->setRequirement('_entity_type', $resource_type->getEntityTypeId())
         ->setRequirement('_bundle', $resource_type->getBundle())
         ->setRequirement('_permission', 'access content')
-        ->setRequirement('_format', 'api_json')
         ->setRequirement('_jsonapi_custom_query_parameter_names', 'TRUE')
         ->setOption('parameters', $parameters)
         ->setOption('_auth', $this->authProviderList())
@@ -148,7 +145,6 @@ class Routes implements ContainerInjectionInterface {
         ->setRequirement('_entity_type', $resource_type->getEntityTypeId())
         ->setRequirement('_bundle', $resource_type->getBundle())
         ->setRequirement('_permission', 'access content')
-        ->setRequirement('_format', 'api_json')
         ->setRequirement('_jsonapi_custom_query_parameter_names', 'TRUE')
         ->setOption('parameters', $parameters)
         ->setOption('_auth', $this->authProviderList())
@@ -161,7 +157,6 @@ class Routes implements ContainerInjectionInterface {
         ->setRequirement('_entity_type', $resource_type->getEntityTypeId())
         ->setRequirement('_bundle', $resource_type->getBundle())
         ->setRequirement('_permission', 'access content')
-        ->setRequirement('_format', 'api_json')
         ->setRequirement('_jsonapi_custom_query_parameter_names', 'TRUE')
         ->setOption('parameters', $parameters)
         ->setOption('_auth', $this->authProviderList())
diff --git a/src/StackMiddleware/FormatSetter.php b/src/StackMiddleware/FormatSetter.php
new file mode 100644
index 0000000..bd7a9aa
--- /dev/null
+++ b/src/StackMiddleware/FormatSetter.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Drupal\jsonapi\StackMiddleware;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+
+/**
+ * If the request belongs to a JSON API managed route, then sets the api_json
+ * format manually.
+ */
+class FormatSetter implements HttpKernelInterface {
+
+  /**
+   * The wrapped HTTP kernel.
+   *
+   * @var \Symfony\Component\HttpKernel\HttpKernelInterface
+   */
+  protected $httpKernel;
+
+  /**
+   * Constructs a PageCache object.
+   *
+   * @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
+   *   The decorated kernel.
+   */
+  public function __construct(HttpKernelInterface $http_kernel) {
+    $this->httpKernel = $http_kernel;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
+    // Check if the accept header is set to the official header.
+    $content_types = array_filter($request->getAcceptableContentTypes(), function ($accept) {
+      return strpos($accept, 'application/vnd.api+json') !== FALSE;
+    });
+    if (count($content_types)) {
+      // Manually set the format.
+      $request->setRequestFormat('api_json');
+    }
+
+    return $this->httpKernel->handle($request, $type, $catch);
+  }
+
+}
diff --git a/tests/src/Functional/JsonApiFunctionalTest.php b/tests/src/Functional/JsonApiFunctionalTest.php
index 869d3f1..6ccfb78 100644
--- a/tests/src/Functional/JsonApiFunctionalTest.php
+++ b/tests/src/Functional/JsonApiFunctionalTest.php
@@ -244,6 +244,21 @@ class JsonApiFunctionalTest extends JsonApiFunctionalTestBase {
     ]));
     $this->assertSession()->statusCodeEquals(200);
     $this->assertGreaterThanOrEqual(1, count($single_output['data']));
+    // 19. Test non-existing route without negotiation header.
+    $this->drupalGet('/jsonapi/node/article/broccoli');
+    $this->assertSession()->statusCodeEquals(404);
+    // Without the Accept header we cannot know we want the 404 error formatted
+    // as JSON API.
+    $this->assertSession()->responseHeaderContains('Content-Type', 'text/html');
+    // 20. Test non-existing route with negotiation header.
+    $single_output = Json::decode($this->drupalGet('/jsonapi/node/article/broccoli', [], [
+      'Accept' => 'application/vnd.api+json',
+    ]));
+    $this->assertEquals(404, $single_output['errors'][0]['status']);
+    $this->assertSession()->statusCodeEquals(404);
+    // With the Accept header we can know we want the 404 error formatted as
+    // JSON API.
+    $this->assertSession()->responseHeaderContains('Content-Type', 'application/vnd.api+json');
   }
 
   /**
diff --git a/tests/src/Functional/JsonApiFunctionalTestBase.php b/tests/src/Functional/JsonApiFunctionalTestBase.php
index 18a0acd..1a648f3 100644
--- a/tests/src/Functional/JsonApiFunctionalTestBase.php
+++ b/tests/src/Functional/JsonApiFunctionalTestBase.php
@@ -150,17 +150,6 @@ abstract class JsonApiFunctionalTestBase extends BrowserTestBase {
   }
 
   /**
-   * {@inheritdoc}
-   */
-  protected function drupalGet($path, array $options = array(), array $headers = array()) {
-    // Make sure we don't forget the format parameter.
-    $options += ['query' => []];
-    $options['query'] += ['_format' => 'api_json'];
-
-    return parent::drupalGet($path, $options, $headers);
-  }
-
-  /**
    * Performs a HTTP request. Wraps the Guzzle HTTP client.
    *
    * Why wrap the Guzzle HTTP client? Because any error response is returned via
@@ -178,7 +167,6 @@ abstract class JsonApiFunctionalTestBase extends BrowserTestBase {
    * @return \Psr\Http\Message\ResponseInterface
    */
   protected function request($method, Url $url, array $request_options) {
-    $url->setOption('query', ['_format' => 'api_json']);
     try {
       $response = $this->httpClient->request($method, $url->toString(), $request_options);
     }
diff --git a/tests/src/Functional/RestJsonApiFormatUnsupported.php b/tests/src/Functional/RestJsonApiFormatUnsupported.php
deleted file mode 100644
index 645f779..0000000
--- a/tests/src/Functional/RestJsonApiFormatUnsupported.php
+++ /dev/null
@@ -1,105 +0,0 @@
-<?php
-
-namespace Drupal\Tests\jsonapi\Functional;
-
-use Drupal\Component\Serialization\Json;
-use Drupal\Core\Url;
-use Drupal\node\Entity\Node;
-use Drupal\node\Entity\NodeType;
-use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
-use Drupal\Tests\rest\Functional\ResourceTestBase;
-
-/**
- * @group jsonapi
- */
-class RestJsonApiFormatUnsupported extends ResourceTestBase {
-
-  use AnonResourceTestTrait;
-
-  /**
-   * {@inheritdoc}
-   */
-  public static $modules = ['jsonapi', 'node'];
-
-  /**
-   * {@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) {
-    $this->grantPermissionsToTestedRole(['access content']);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setUp() {
-    parent::setUp();
-
-    // Set up a HTTP client that accepts relative URLs.
-    $this->httpClient = $this->container->get('http_client_factory')
-      ->fromOptions(['base_uri' => $this->baseUrl]);
-
-    // Create a "Camelids" node type.
-    NodeType::create([
-      'name' => 'Camelids',
-      'type' => 'camelids',
-    ])->save();
-
-    // Create a "Llama" node.
-    $node = Node::create(['type' => 'camelids']);
-    $node->setTitle('Llama')
-      ->setOwnerId(0)
-      ->setPublished(TRUE)
-      ->save();
-  }
-
-  /**
-   * Tests that JSON API is not supported
-   */
-  public function testJsonApiFormatNotSupportedInRest() {
-    // First, verify that 'api_json' does not appear in the 'serializer.foramts'
-    // container parameter, which is what the REST module uses to determine
-    // which formats it supports.
-    $this->assertSame(['json', 'xml'], $this->container->getParameter('serializer.formats'));
-
-    // Second, verify that provisioning a REST resource that lists 'api_json' as
-    // one of its formats
-    $this->provisionResource(['api_json'], []);
-    $this->setUpAuthorization('GET');
-    $url = Node::load(1)->toUrl()->setOption('query', ['_format' => 'api_json']);
-    $response = $this->request('GET', $url, []);
-    $expected_body = Json::encode([
-      'errors' => [
-        [
-          'title' => 'Not Acceptable',
-          'status' => 406,
-          'detail' => 'Not acceptable format: api_json',
-          'links' => [
-            'info' => 'http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.7',
-          ],
-          'code' => 0,
-        ],
-      ],
-    ]);
-    $this->assertResourceResponse(406, $expected_body, $response);
-  }
-
-  protected function assertNormalizationEdgeCases($method, Url $url, array $request_options) {}
-  protected function getExpectedUnauthorizedAccessMessage($method) {}
-  protected function getExpectedBcUnauthorizedAccessMessage($method) {}
-
-}
diff --git a/tests/src/Functional/RestJsonApiUnsupported.php b/tests/src/Functional/RestJsonApiUnsupported.php
index 6e026f6..c8ac3e3 100644
--- a/tests/src/Functional/RestJsonApiUnsupported.php
+++ b/tests/src/Functional/RestJsonApiUnsupported.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Tests\jsonapi\Functional;
 
-use Drupal\Component\Serialization\Json;
 use Drupal\Core\Url;
 use Drupal\node\Entity\Node;
 use Drupal\node\Entity\NodeType;
@@ -87,20 +86,7 @@ class RestJsonApiUnsupported extends ResourceTestBase {
     $request_options = [];
 
     $response = $this->request('GET', $url, $request_options);
-    $expected_body = Json::encode([
-      'errors' => [
-        [
-          'title' => 'Not Acceptable',
-          'status' => 406,
-          'detail' => 'Not acceptable format: api_json',
-          'links' => [
-            'info' => 'http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.7',
-          ],
-          'code' => 0,
-        ],
-      ],
-    ]);
-    $this->assertResourceResponse(406, $expected_body, $response);
+    $this->assertResourceErrorResponse(406, FALSE, $response);
   }
 
   protected function assertNormalizationEdgeCases($method, Url $url, array $request_options) {}
