diff --git a/core/core.services.yml b/core/core.services.yml
index afccb31..cf35062 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -885,6 +885,10 @@ services:
     class: Drupal\Core\Routing\RequestFormatRouteFilter
     tags:
       - { name: route_filter }
+  method_filter:
+    class: Drupal\Core\Routing\MethodFilter
+    tags:
+      - { name: route_filter, priority: 1 }
   content_type_header_matcher:
     class: Drupal\Core\Routing\ContentTypeHeaderMatcher
     tags:
diff --git a/core/lib/Drupal/Core/EventSubscriber/ExceptionJsonSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ExceptionJsonSubscriber.php
index c06adee..399cdb9 100644
--- a/core/lib/Drupal/Core/EventSubscriber/ExceptionJsonSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/ExceptionJsonSubscriber.php
@@ -76,4 +76,15 @@ public function on406(GetResponseForExceptionEvent $event) {
     $event->setResponse($response);
   }
 
+  /**
+   * Handles a 415 error for JSON.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
+   *   The event to process.
+   */
+  public function on415(GetResponseForExceptionEvent $event) {
+    $response = new JsonResponse(['message' => $event->getException()->getMessage()], Response::HTTP_UNSUPPORTED_MEDIA_TYPE);
+    $event->setResponse($response);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Routing/MethodFilter.php b/core/lib/Drupal/Core/Routing/MethodFilter.php
new file mode 100644
index 0000000..64b8048
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/MethodFilter.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Routing\MethodFilter.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Filters routes based on the HTTP method.
+ */
+class MethodFilter implements RouteFilterInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function filter(RouteCollection $collection, Request $request) {
+    $method = $request->getMethod();
+
+    $all_supported_methods = [];
+
+    foreach ($collection->all() as $name => $route) {
+      $supported_methods = $route->getMethods();
+      // If the GET method is allowed we also need to allow the HEAD method
+      // since HEAD is a GET method that doesn't return the body.
+      if (in_array('GET', $supported_methods)) {
+        $supported_methods[] = 'HEAD';
+      }
+
+      // A route not restricted to specific methods allows any method. If this
+      // is the case, we'll also have at least one route left in the collection,
+      // hence we don't need to calculate the set of all supported methods.
+      if (empty($supported_methods)) {
+        continue;
+      }
+      elseif (!in_array($method, $supported_methods)) {
+        $all_supported_methods = array_merge($supported_methods, $all_supported_methods);
+        $collection->remove($name);
+      }
+    }
+    if (count($collection)) {
+      return $collection;
+    }
+    throw new MethodNotAllowedException(array_unique($all_supported_methods));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applies(Route $route) {
+    return TRUE;
+  }
+
+}
diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php
index db7d123..2184cfc 100644
--- a/core/modules/rest/src/Tests/RESTTestBase.php
+++ b/core/modules/rest/src/Tests/RESTTestBase.php
@@ -158,6 +158,10 @@ protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL) {
         break;
     }
 
+    if ($mime_type === 'none') {
+      unset($curl_options[CURLOPT_HTTPHEADER]['Content-Type']);
+    }
+
     $this->responseBody = $this->curlExec($curl_options);
 
     // Ensure that any changes to variables in the other thread are picked up.
diff --git a/core/modules/rest/src/Tests/UpdateTest.php b/core/modules/rest/src/Tests/UpdateTest.php
index ef18a74..93d3b6f 100644
--- a/core/modules/rest/src/Tests/UpdateTest.php
+++ b/core/modules/rest/src/Tests/UpdateTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\rest\Tests;
 
 use Drupal\Component\Serialization\Json;
+use Symfony\Component\HttpFoundation\Response;
 
 /**
  * Tests the update of resources.
@@ -55,6 +56,12 @@ public function testPatchUpdate() {
     $patch_entity->set('uuid', NULL);
     $serialized = $serializer->serialize($patch_entity, $this->defaultFormat, $context);
 
+    // Update the entity over the REST API but forget to specify a Content-Type
+    // header, this should throw the proper exception.
+    $this->httpRequest($entity->toUrl(), 'PATCH', $serialized, 'none');
+    $this->assertResponse(Response::HTTP_UNSUPPORTED_MEDIA_TYPE);
+    $this->assertRaw('No route found that matches &quot;Content-Type: none&quot;');
+
     // Update the entity over the REST API.
     $this->httpRequest($entity->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
     $this->assertResponse(204);
diff --git a/core/modules/system/src/Tests/Routing/ExceptionHandlingTest.php b/core/modules/system/src/Tests/Routing/ExceptionHandlingTest.php
index ee3b6eb..c269734 100644
--- a/core/modules/system/src/Tests/Routing/ExceptionHandlingTest.php
+++ b/core/modules/system/src/Tests/Routing/ExceptionHandlingTest.php
@@ -35,6 +35,19 @@ protected function setUp() {
   }
 
   /**
+   * Tests on a route with a non-supported HTTP method.
+   */
+  public function test405() {
+    $request = Request::create('/router_test/test15', 'PATCH');
+
+    /** @var \Symfony\Component\HttpKernel\HttpKernelInterface $kernel */
+    $kernel = \Drupal::getContainer()->get('http_kernel');
+    $response = $kernel->handle($request);
+
+    $this->assertEqual(Response::HTTP_METHOD_NOT_ALLOWED, $response->getStatusCode());
+  }
+
+  /**
    * Tests the exception handling for json and 403 status code.
    */
   public function testJson403() {
diff --git a/core/tests/Drupal/Tests/Core/Routing/MethodFilterTest.php b/core/tests/Drupal/Tests/Core/Routing/MethodFilterTest.php
new file mode 100644
index 0000000..e55d131
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Routing/MethodFilterTest.php
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Routing\MethodFilterTest.
+ */
+
+namespace Drupal\Tests\Core\Routing;
+
+use Drupal\Core\Routing\MethodFilter;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Routing\MethodFilter
+ * @group Routing
+ */
+class MethodFilterTest extends \PHPUnit_Framework_TestCase {
+
+  /**
+   * @covers ::filter
+   */
+  public function testWithAllowedMethod() {
+    $request = Request::create('/test', 'GET');
+    $collection = new RouteCollection();
+    $collection->add('test_route.get', new Route('/test', [], [], [], '', [], ['GET']));
+    $collection_before = clone $collection;
+
+    $method_filter = new MethodFilter();
+    $result_collection = $method_filter->filter($collection, $request);
+
+    $this->assertEquals($collection_before, $result_collection);
+  }
+
+  /**
+   * @covers ::filter
+   */
+  public function testWithAllowedMethodAndMultipleMatchingRoutes() {
+    $request = Request::create('/test', 'GET');
+    $collection = new RouteCollection();
+    $collection->add('test_route.get', new Route('/test', [], [], [], '', [], ['GET']));
+    $collection->add('test_route2.get', new Route('/test', [], [], [], '', [], ['GET']));
+    $collection->add('test_route3.get', new Route('/test', [], [], [], '', [], ['GET']));
+
+    $collection_before = clone $collection;
+
+    $method_filter = new MethodFilter();
+    $result_collection = $method_filter->filter($collection, $request);
+
+    $this->assertEquals($collection_before, $result_collection);
+  }
+
+  /**
+   * @covers ::filter
+   */
+  public function testMethodNotAllowedException() {
+    $request = Request::create('/test', 'PATCH');
+    $collection = new RouteCollection();
+    $collection->add('test_route.get', new Route('/test', [], [], [], '', [], ['GET']));
+
+    $this->setExpectedException(MethodNotAllowedException::class);
+
+    $method_filter = new MethodFilter();
+    $method_filter->filter($collection, $request);
+  }
+
+  /**
+   * @covers ::filter
+   */
+  public function testMethodNotAllowedExceptionWithMultipleRoutes() {
+    $request = Request::create('/test', 'PATCH');
+    $collection = new RouteCollection();
+    $collection->add('test_route.get', new Route('/test', [], [], [], '', [], ['GET']));
+    $collection->add('test_route2.get', new Route('/test', [], [], [], '', [], ['GET']));
+    $collection->add('test_route3.get', new Route('/test', [], [], [], '', [], ['GET']));
+
+    $this->setExpectedException(MethodNotAllowedException::class);
+
+    $method_filter = new MethodFilter();
+    $method_filter->filter($collection, $request);
+  }
+
+  /**
+   * @covers ::filter
+   */
+  public function testFilteredMethods() {
+    $request = Request::create('/test', 'PATCH');
+    $collection = new RouteCollection();
+    $collection->add('test_route.get', new Route('/test', [], [], [], '', [], ['GET']));
+    $collection->add('test_route2.get', new Route('/test', [], [], [], '', [], ['PATCH']));
+    $collection->add('test_route3.get', new Route('/test', [], [], [], '', [], ['POST']));
+
+    $expected_collection = new RouteCollection();
+    $expected_collection->add('test_route2.get', new Route('/test', [], [], [], '', [], ['PATCH']));
+
+    $method_filter = new MethodFilter();
+    $result_collection = $method_filter->filter($collection, $request);
+
+    $this->assertEquals($expected_collection, $result_collection);
+  }
+
+}
