diff --git a/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php b/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php
index f246cce462..9a9fc8d10f 100644
--- a/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php
+++ b/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Core\StackMiddleware;
 
+use Symfony\Component\HttpFoundation\AcceptHeader;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpKernel\HttpKernelInterface;
 
@@ -86,11 +87,30 @@ protected function getContentType(Request $request) {
       return 'iframeupload';
     }
 
+    // If a format is requested via a query parameter, use that.
     if ($request->query->has('_format')) {
       return $request->query->get('_format');
     }
 
-    // No format was specified in the request.
+    // If one and only one media type is requested via the Accept header, use
+    // the corresponding format for that. Browsers typically accept multiple
+    // media types (including a catch-all "*/*"), and we do not attempt to
+    // negotiate for that here, but HTTP API clients sometimes request a single
+    // media type via the Accept header, in which case, that tells us
+    // unambiguously which format is requested. If negotiation among multiple
+    // accepted formats is needed, then a separate middleware with the desired
+    // negotiation logic can be added. For an example,
+    // @see \Drupal\accept_header_routing_test\AcceptHeaderMiddleware
+    $accepted = AcceptHeader::fromString($request->headers->get('Accept'));
+    if (count($accepted->all()) === 1) {
+      $media_type = $accepted->first()->getValue();
+      $format = $request->getFormat($media_type);
+      if ($format) {
+        return $format;
+      }
+    }
+
+    // No unambiguous format was specified in the request.
     return NULL;
   }
 
