diff --git a/core/modules/rest/lib/Drupal/rest/EventSubscriber/RouteSubscriber.php b/core/modules/rest/lib/Drupal/rest/EventSubscriber/RouteSubscriber.php
index d4711e0..965c149 100644
--- a/core/modules/rest/lib/Drupal/rest/EventSubscriber/RouteSubscriber.php
+++ b/core/modules/rest/lib/Drupal/rest/EventSubscriber/RouteSubscriber.php
@@ -54,15 +54,30 @@ public function __construct(ResourcePluginManager $manager, ConfigFactory $confi
   public function dynamicRoutes(RouteBuildEvent $event) {
 
     $collection = $event->getRouteCollection();
+    $enabled_resources = $this->config->get('rest.settings')->load()->get('resources');
 
-    $resources = $this->config->get('rest.settings')->load()->get('resources');
-    if ($resources && $enabled = array_intersect_key($this->manager->getDefinitions(), $resources)) {
-      foreach ($enabled as $key => $resource) {
-        $plugin = $this->manager->getInstance(array('id' => $key));
+    // Iterate over all enabled resource plugins.
+    foreach ($enabled_resources as $id => $enabled_methods) {
+      $plugin = $this->manager->getInstance(array('id' => $id));
 
-        foreach ($plugin->routes() as $name => $route) {
+      foreach ($plugin->routes() as $name => $route) {
+        $method = $route->getRequirement('_method');
+        // Only expose routes where the method is enabled in the configuration.
+        if ($method && isset($enabled_methods[$method])) {
           $route->setRequirement('_access_rest_csrf',  'TRUE');
-          $collection->add("rest.$name", $route);
+
+          // If the array of configured format restrictions is empty for a
+          // method always add the route.
+          if (empty($enabled_methods[$method])) {
+            $collection->add("rest.$name", $route);
+            continue;
+          }
+          // If there is no format requirement or if it matches the
+          // configuration also add the route.
+          $format_requirement = $route->getRequirement('_format');
+          if (!$format_requirement || isset($enabled_methods[$method][$format_requirement])) {
+            $collection->add("rest.$name", $route);
+          }
         }
       }
     }
diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/ResourceBase.php b/core/modules/rest/lib/Drupal/rest/Plugin/ResourceBase.php
index 41f19fd..747df45 100644
--- a/core/modules/rest/lib/Drupal/rest/Plugin/ResourceBase.php
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/ResourceBase.php
@@ -26,15 +26,11 @@
   public function permissions() {
     $permissions = array();
     $definition = $this->getDefinition();
-    foreach ($this->requestMethods() as $method) {
+    foreach ($this->availableMethods() as $method) {
       $lowered_method = strtolower($method);
-      // Only expose permissions where the HTTP request method exists on the
-      // plugin.
-      if (method_exists($this, $lowered_method)) {
-        $permissions["restful $lowered_method $this->plugin_id"] = array(
-          'title' => t('Access @method on %label resource', array('@method' => $method, '%label' => $definition['label'])),
-        );
-      }
+      $permissions["restful $lowered_method $this->plugin_id"] = array(
+        'title' => t('Access @method on %label resource', array('@method' => $method, '%label' => $definition['label'])),
+      );
     }
     return $permissions;
   }
@@ -47,38 +43,44 @@ public function routes() {
     $path_prefix = strtr($this->plugin_id, ':', '/');
     $route_name = strtr($this->plugin_id, ':', '.');
 
-    $methods = $this->requestMethods();
+    $methods = $this->availableMethods();
     foreach ($methods as $method) {
       $lower_method = strtolower($method);
-      // Only expose routes where the HTTP request method exists on the plugin.
-      if (method_exists($this, $lower_method)) {
-        $route = new Route("/$path_prefix/{id}", array(
-          '_controller' => 'Drupal\rest\RequestHandler::handle',
-          // Pass the resource plugin ID along as default property.
-          '_plugin' => $this->plugin_id,
-        ), array(
-          // The HTTP method is a requirement for this route.
-          '_method' => $method,
-          '_permission' => "restful $lower_method $this->plugin_id",
-        ));
+      $route = new Route("/$path_prefix/{id}", array(
+        '_controller' => 'Drupal\rest\RequestHandler::handle',
+        // Pass the resource plugin ID along as default property.
+        '_plugin' => $this->plugin_id,
+      ), array(
+        // The HTTP method is a requirement for this route.
+        '_method' => $method,
+        '_permission' => "restful $lower_method $this->plugin_id",
+      ));
 
-        switch ($method) {
-          case 'POST':
-            // POST routes do not require an ID in the URL path.
-            $route->setPattern("/$path_prefix");
-            $route->addDefaults(array('id' => NULL));
-            break;
+      switch ($method) {
+        case 'POST':
+          // POST routes do not require an ID in the URL path.
+          $route->setPattern("/$path_prefix");
+          $route->addDefaults(array('id' => NULL));
+          $collection->add("$route_name.$method", $route);
+          break;
 
-          case 'GET':
-          case 'HEAD':
-            // Restrict GET and HEAD requests to the media type specified in the
-            // HTTP Accept headers.
-            // @todo Replace hard coded format here with available formats.
-            $route->addRequirements(array('_format' => 'drupal_jsonld'));
-            break;
-        }
+        case 'GET':
+        case 'HEAD':
+          // Restrict GET and HEAD requests to the media type specified in the
+          // HTTP Accept headers.
+          $formats = drupal_container()->getParameter('serializer.formats');
+          foreach ($formats as $format_name => $label) {
+            // Expose one route per available format.
+            //$format_route = new Route($route->getPattern(), $route->getDefaults(), $route->getRequirements());
+            $format_route = clone $route;
+            $format_route->addRequirements(array('_format' => $format_name));
+            $collection->add("$route_name.$method.$format_name", $format_route);
+          }
+          break;
 
-        $collection->add("$route_name.$method", $route);
+        default:
+          $collection->add("$route_name.$method", $route);
+          break;
       }
     }
 
@@ -95,7 +97,7 @@ public function routes() {
    *   The list of allowed HTTP request method strings.
    */
   protected function requestMethods() {
-    return drupal_map_assoc(array(
+    return array(
       'HEAD',
       'GET',
       'POST',
@@ -105,6 +107,21 @@ protected function requestMethods() {
       'OPTIONS',
       'CONNECT',
       'PATCH',
-    ));
+    );
+  }
+
+  /**
+   * Implements ResourceInterface::availableMethods().
+   */
+  public function availableMethods() {
+    $methods = $this->requestMethods();
+    $available = array();
+    foreach ($methods as $method) {
+      // Only expose methods where the HTTP request method exists on the plugin.
+      if (method_exists($this, strtolower($method))) {
+        $available[] = $method;
+      }
+    }
+    return $available;
   }
 }
diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/ResourceInterface.php b/core/modules/rest/lib/Drupal/rest/Plugin/ResourceInterface.php
index 87da39d..3f21f0d 100644
--- a/core/modules/rest/lib/Drupal/rest/Plugin/ResourceInterface.php
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/ResourceInterface.php
@@ -36,4 +36,12 @@ public function routes();
    */
   public function permissions();
 
+  /**
+   * Returns the available HTTP request methods on this plugin.
+   *
+   * @return array
+   *   The list of supported methods. Example: array('GET', 'POST', 'PATCH').
+   */
+  public function availableMethods();
+
 }
diff --git a/core/modules/rest/lib/Drupal/rest/RequestHandler.php b/core/modules/rest/lib/Drupal/rest/RequestHandler.php
index fe74eec..1c0b56a 100644
--- a/core/modules/rest/lib/Drupal/rest/RequestHandler.php
+++ b/core/modules/rest/lib/Drupal/rest/RequestHandler.php
@@ -12,6 +12,7 @@
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\HttpException;
+use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
 
 /**
  * Acts as intermediate request forwarder for resource plugins.
@@ -42,11 +43,22 @@ public function handle(Request $request, $id = NULL) {
     $received = $request->getContent();
     $unserialized = NULL;
     if (!empty($received)) {
-      $definition = $resource->getDefinition();
-      $class = $definition['serialization_class'];
-      // @todo Replace the format here with something we get from the HTTP
-      //   Content-type header. See http://drupal.org/node/1850704
-      $unserialized = $serializer->deserialize($received, $class, 'drupal_jsonld');
+      $format = $request->getContentType();
+
+      // Only allow serialization formats that are explicitly configured. If no
+      // formats are configured allow all and hope that the serializer knows the
+      // format. If the serializer cannot handle it an exception will be thrown
+      // that bubbles up to the client.
+      $config = $this->container->get('config.factory')->get('rest.settings')->get('resources');
+      $enabled_formats = $config[$plugin][$request->getMethod()];
+      if (empty($enabled_formats) || isset($enabled_formats[$format])) {
+        $definition = $resource->getDefinition();
+        $class = $definition['serialization_class'];
+        $unserialized = $serializer->deserialize($received, $class, $format);
+      }
+      else {
+        throw new UnsupportedMediaTypeHttpException();
+      }
     }
 
     // Invoke the operation on the resource plugin.
@@ -60,11 +72,15 @@ public function handle(Request $request, $id = NULL) {
     // Serialize the outgoing data for the response, if available.
     $data = $response->getResponseData();
     if ($data != NULL) {
-      // @todo Replace the format here with something we get from the HTTP
-      //   Accept headers. See http://drupal.org/node/1833440
-      $output = $serializer->serialize($data, 'drupal_jsonld');
+      // All REST routes are restricted to exactly one format, so instead of
+      // parsing it out of the Accept headers again we can simply retrieve the
+      // format requirement. If there is no format associated just pick Drupal
+      // JSON-LD.
+      $format = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)->getRequirement('_format') ?: 'drupal_jsonld';
+
+      $output = $serializer->serialize($data, $format);
       $response->setContent($output);
-      $response->headers->set('Content-Type', 'application/vnd.drupal.ld+json');
+      $response->headers->set('Content-Type', $request->getMimeType($format));
     }
     return $response;
   }
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php b/core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php
index c5b298a..3d45129 100644
--- a/core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php
+++ b/core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php
@@ -38,7 +38,7 @@ public function testCreate() {
     // entity types here as well.
     $entity_type = 'entity_test';
 
-    $this->enableService('entity:' . $entity_type);
+    $this->enableService('entity:' . $entity_type, 'POST');
     // Create a user account that has the required permissions to create
     // resources via the web API.
     $account = $this->drupalCreateUser(array('restful post entity:' . $entity_type));
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php b/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php
index ed2d04d..ac65bfc 100644
--- a/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php
+++ b/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php
@@ -36,7 +36,7 @@ public function testDelete() {
     // Define the entity types we want to test.
     $entity_types = array('entity_test', 'node', 'user');
     foreach ($entity_types as $entity_type) {
-      $this->enableService('entity:' . $entity_type);
+      $this->enableService('entity:' . $entity_type, 'DELETE');
       // Create a user account that has the required permissions to delete
       // resources via the web API.
       $account = $this->drupalCreateUser(array('restful delete entity:' . $entity_type));
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
index f7dbdb6..c428c86 100644
--- a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
+++ b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
@@ -156,18 +156,24 @@ protected function entityValues($entity_type) {
    * @param string|FALSE $resource_type
    *   The resource type that should get web API enabled or FALSE to disable all
    *   resource types.
+   * @param string $method
+   *   The HTTP method to enable, e.g. GET, POST etc.
+   * @param string $format
+   *   (Optional) The serialization format, e.g. jsonld.
    */
-  protected function enableService($resource_type) {
+  protected function enableService($resource_type, $method = 'GET', $format = NULL) {
     // Enable web API for this entity type.
     $config = config('rest.settings');
+    $settings = array();
     if ($resource_type) {
-      $config->set('resources', array(
-        $resource_type => $resource_type,
-      ));
-    }
-    else {
-      $config->set('resources', array());
+      if ($format) {
+        $settings[$resource_type][$method][$format] = 'TRUE';
+      }
+      else {
+        $settings[$resource_type][$method] = array();
+      }
     }
+    $config->set('resources', $settings);
     $config->save();
 
     // Rebuild routing cache, so that the web API paths are available.
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php b/core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php
index 0578fc3..4f49b69 100644
--- a/core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php
+++ b/core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php
@@ -38,7 +38,7 @@ public function testRead() {
     // Define the entity types we want to test.
     $entity_types = array('entity_test');
     foreach ($entity_types as $entity_type) {
-      $this->enableService('entity:' . $entity_type);
+      $this->enableService('entity:' . $entity_type, 'GET');
       // Create a user account that has the required permissions to delete
       // resources via the web API.
       $account = $this->drupalCreateUser(array('restful get entity:' . $entity_type));
@@ -57,12 +57,8 @@ public function testRead() {
       $this->assertEqual($data['uuid'][LANGUAGE_DEFAULT][0]['value'], $entity->uuid(), 'Entity UUID is correct');
 
       // Try to read the entity with an unsupported mime format.
-      // Because the matcher checks mime type first, then method, this will hit
-      // zero viable routes on the method.  If the mime matcher wasn't working,
-      // we would still find an existing GET route with the wrong format. That
-      // means this is a valid functional test for mime-matching.
       $response = $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'GET', NULL, 'application/wrongformat');
-      $this->assertResponse(405);
+      $this->assertResponse(415);
 
       // Try to read an entity that does not exist.
       $response = $this->httpRequest('entity/' . $entity_type . '/9999', 'GET', NULL, 'application/vnd.drupal.ld+json');
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/UpdateTest.php b/core/modules/rest/lib/Drupal/rest/Tests/UpdateTest.php
index fa65a2f..94be2bf 100644
--- a/core/modules/rest/lib/Drupal/rest/Tests/UpdateTest.php
+++ b/core/modules/rest/lib/Drupal/rest/Tests/UpdateTest.php
@@ -38,7 +38,7 @@ public function testPatchUpdate() {
     // entity types here as well.
     $entity_type = 'entity_test';
 
-    $this->enableService('entity:' . $entity_type);
+    $this->enableService('entity:' . $entity_type, 'PATCH');
     // Create a user account that has the required permissions to create
     // resources via the web API.
     $account = $this->drupalCreateUser(array('restful patch entity:' . $entity_type));
@@ -103,7 +103,7 @@ public function testPutUpdate() {
     // entity types here as well.
     $entity_type = 'entity_test';
 
-    $this->enableService('entity:' . $entity_type);
+    $this->enableService('entity:' . $entity_type, 'PUT');
     // Create a user account that has the required permissions to create
     // resources via the web API.
     $account = $this->drupalCreateUser(array('restful put entity:' . $entity_type));
diff --git a/core/modules/rest/rest.admin.inc b/core/modules/rest/rest.admin.inc
index 8151cb8..6e0c1e5 100644
--- a/core/modules/rest/rest.admin.inc
+++ b/core/modules/rest/rest.admin.inc
@@ -11,49 +11,104 @@
  * @ingroup forms
  */
 function rest_admin_form($form, &$form_state) {
-  $resources = drupal_container()
-    ->get('plugin.manager.rest')
-    ->getDefinitions();
+  $manager = drupal_container()
+    ->get('plugin.manager.rest');
+  $resources = $manager->getDefinitions();
+  $formats = drupal_container()->getParameter('serializer.formats');
+  $enabled_resources = config('rest.settings')->get('resources') ?: array();
+
   $entity_resources = array();
   $other_resources = array();
   foreach ($resources as $plugin_name => $definition) {
     if (strpos($plugin_name, 'entity:') === FALSE) {
-      $other_resources[$plugin_name] = $definition['label'];
+      $other_resources[$plugin_name] = $definition;
     }
     else {
-      $entity_resources[$plugin_name] = $definition['label'];
+      $entity_resources[$plugin_name] = $definition;
     }
   }
-  asort($entity_resources);
-  asort($other_resources);
-  $enabled_resources = config('rest.settings')->get('resources') ?: array();
+
+  // Sort the available resource plugins per label.
+  $sorter = function ($a, $b) {
+    return strnatcasecmp($a['label'], $b['label']);
+  };
+  uasort($entity_resources, $sorter);
+  uasort($other_resources, $sorter);
 
   $form['entity_resources'] = array(
-    '#type' => 'checkboxes',
-    '#options' => $entity_resources,
-    '#default_value' => $enabled_resources,
+    '#type' => 'fieldset',
     '#title' => t('Entity resource types that should be exposed as web services:'),
-  );
+    '#tree' => TRUE,
+  ) + rest_admin_resource_fieldset('entity_resources', $entity_resources, $enabled_resources, $manager, $formats);
+
   if (!empty($other_resources)) {
     $form['other_resources'] = array(
-      '#type' => 'checkboxes',
-      '#options' => $other_resources,
-      '#default_value' => $enabled_resources,
+      '#type' => 'fieldset',
       '#title' => t('Other available resource types that should be exposed as web services:'),
-    );
+      '#tree' => TRUE,
+    ) + rest_admin_resource_fieldset('other_resources', $other_resources, $enabled_resources, $manager, $formats);
   }
 
   return system_config_form($form, $form_state);
 }
 
 /**
+ * Form building helper function for the nested configuration fieldsets.
+ */
+function rest_admin_resource_fieldset($category, $resources, $enabled_resources, $manager, $formats) {
+  $form = array();
+  foreach ($resources as $id => $definition) {
+    $resource = $manager->getInstance(array('id' => $id));
+    $form[$id]['enabled'] = array(
+      '#type' => 'checkbox',
+      '#title' => $definition['label'],
+      '#default_value' => isset($enabled_resources[$id]),
+    );
+    $form[$id]['operations'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Operations'),
+      '#description' => t('Select the operations that should be exposed.'),
+      '#states' => array(
+        'invisible' => array(
+          'input[name="' . $category . '[' . $id . '][enabled]"]' => array('checked' => FALSE),
+        ),
+      ),
+    );
+
+    foreach ($resource->availableMethods() as $name) {
+      $form[$id]['operations'][$name]['enabled'] = array(
+        '#type' => 'checkbox',
+        '#title' => $name,
+        '#default_value' => isset($enabled_resources[$id][$name]),
+      );
+      $form[$id]['operations'][$name]['formats'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Formats'),
+        '#description' => t('Optional: select requerst/response formats that should be allowed for the @operation operation.', array('@operation' => $name)),
+        '#states' => array(
+          'invisible' => array(
+            'input[name="' . $category . '[' . $id . '][operations][' . $name . '][enabled]"]' => array('checked' => FALSE),
+          ),
+        ),
+      );
+      foreach ($formats as $format_name => $format_label) {
+        $form[$id]['operations'][$name]['formats'][$format_name] = array(
+          '#type' => 'checkbox',
+          '#title' => $format_label,
+          '#default_value' => !empty($enabled_resources[$id][$name][$format_name]),
+        );
+      }
+    }
+  }
+  return $form;
+}
+
+/**
  * Form submission handler for rest_admin_form().
  */
 function rest_admin_form_submit($form, &$form_state) {
-  $resources = array_filter($form_state['values']['entity_resources']);
-  if (!empty($form_state['values']['other_resources'])) {
-    $resources += array_filter($form_state['values']['other_resources']);
-  }
+  $resources = rest_admin_extract_submission($form_state['values']['entity_resources'])
+    + rest_admin_extract_submission($form_state['values']['other_resources']);
 
   $config = config('rest.settings');
   $config->set('resources', $resources);
@@ -62,3 +117,25 @@ function rest_admin_form_submit($form, &$form_state) {
   // Rebuild routing cache.
   drupal_container()->get('router.builder')->rebuild();
 }
+
+/**
+ * Form submission helper to build the configuration array.
+ */
+function rest_admin_extract_submission($values) {
+  $resources = array();
+  foreach ($values as $plugin => $settings) {
+    if ($settings['enabled']) {
+      foreach ($settings['operations'] as $operation => $operation_settings) {
+        if ($operation_settings['enabled']) {
+          $resources[$plugin][$operation] = array();
+          foreach ($operation_settings['formats'] as $format => $enabled) {
+            if ($enabled) {
+              $resources[$plugin][$operation][$format] = 'TRUE';
+            }
+          }
+        }
+      }
+    }
+  }
+  return $resources;
+}
