diff --git a/core/lib/Drupal/Core/ContentNegotiation.php b/core/lib/Drupal/Core/ContentNegotiation.php
new file mode 100644
index 0000000000000000000000000000000000000000..f1a33d724d232b5e1d4dbdf0465c4ac4b9cb3348
--- /dev/null
+++ b/core/lib/Drupal/Core/ContentNegotiation.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\Core;
+
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Description of ContentNegotiation
+ *
+ */
+class ContentNegotiation {
+
+  public function getContentType(Request $request) {
+    $acceptable_content_types = $request->getAcceptableContentTypes();
+    if (in_array('application/json', $request->getAcceptableContentTypes())) {
+      return 'json';
+    }
+    if(in_array('text/html', $acceptable_content_types) || in_array('*/*', $acceptable_content_types)) {
+      return 'html';
+    }
+  }
+
+}
+
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
index d601f7d7fef61da329b1181d5d93d2029d35004d..605f2ce84e85b7db2334b3f1bbdd684aff59a891 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -13,8 +13,7 @@
 use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
 use Symfony\Component\HttpKernel\EventListener\RouterListener;
 use Symfony\Component\HttpKernel\EventListener\ExceptionListener;
-use Drupal\Core\EventSubscriber\HtmlSubscriber;
-use Drupal\Core\EventSubscriber\JsonSubscriber;
+use Drupal\Core\EventSubscriber\ViewSubscriber;
 use Drupal\Core\EventSubscriber\AccessSubscriber;
 use Drupal\Core\EventSubscriber\PathSubscriber;
 use Drupal\Core\EventSubscriber\LegacyControllerSubscriber;
@@ -52,11 +51,12 @@ public function __construct(EventDispatcherInterface $dispatcher, ControllerReso
       $this->matcher = new UrlMatcher($context);
       $this->dispatcher->addSubscriber(new RouterListener($this->matcher));
 
+      $negotiation = new ContentNegotiation();
+
       // @todo Make this extensible rather than just hard coding some.
       // @todo Add a subscriber to handle other things, too, like our Ajax
       // replacement system.
-      $this->dispatcher->addSubscriber(new HtmlSubscriber());
-      $this->dispatcher->addSubscriber(new JsonSubscriber());
+      $this->dispatcher->addSubscriber(new ViewSubscriber($negotiation));
       $this->dispatcher->addSubscriber(new AccessSubscriber());
       $this->dispatcher->addSubscriber(new PathSubscriber());
       $this->dispatcher->addSubscriber(new LegacyControllerSubscriber());
@@ -67,6 +67,6 @@ public function __construct(EventDispatcherInterface $dispatcher, ControllerReso
       // Either way, treat it as a server-level error and return an HTTP 500.
       // By default, this will be an HTML-type response because that's a decent
       // best guess if we don't know otherwise.
-      $this->dispatcher->addSubscriber(new ExceptionListener(array(new ExceptionController(), 'execute')));
+      $this->dispatcher->addSubscriber(new ExceptionListener(array(new ExceptionController($negotiation), 'execute')));
     }
 }
diff --git a/core/lib/Drupal/Core/EventSubscriber/HtmlSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/HtmlSubscriber.php
deleted file mode 100644
index e10f97d68db2948a7b95fd13394f93ba3f6316ad..0000000000000000000000000000000000000000
--- a/core/lib/Drupal/Core/EventSubscriber/HtmlSubscriber.php
+++ /dev/null
@@ -1,77 +0,0 @@
-<?php
-
-namespace Drupal\Core\EventSubscriber;
-
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\Response;
-use Symfony\Component\HttpKernel\KernelEvents;
-use Symfony\Component\HttpKernel\Event\GetResponseEvent;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
-use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
-use Symfony\Component\Routing\Exception\ResourceNotFoundException;
-use Symfony\Component\Routing\Exception\MethodNotAllowedException;
-use Symfony\Component\EventDispatcher\EventSubscriberInterface;
-
-use Drupal\Core\DrupalKernel;
-
-/**
- * @file
- *
- * Definition of Drupal\Core\EventSubscriber\HtmlSubscriber;
- */
-
-/**
- * Main subscriber for HTML-type HTTP responses.
- */
-class HtmlSubscriber implements EventSubscriberInterface {
-
-  /**
-   * Determines if we are dealing with an HTML-style response.
-   *
-   * @param GetResponseEvent $event
-   *   The Event to process.
-   * @return boolean
-   *   True if it is an event we should process as HTML, False otherwise.
-   */
-  protected function isHtmlRequestEvent(GetResponseEvent $event) {
-    $acceptable_content_types = $event->getRequest()->getAcceptableContentTypes();
-    return in_array('text/html', $acceptable_content_types) || in_array('*/*', $acceptable_content_types);
-  }
-
-  /**
-   * Processes a successful controller into an HTTP 200 response.
-   *
-   * Some controllers may not return a response object but simply the body of
-   * one.  The VIEW event is called in that case, to allow us to mutate that
-   * body into a Response object.  In particular we assume that the return
-   * from an HTML-type response is a render array from a legacy page callback
-   * and render it.
-   *
-   * @param GetResponseEvent $event
-   *   The Event to process.
-   */
-  public function onView(GetResponseEvent $event) {
-    if ($this->isHtmlRequestEvent($event)) {
-      $page_callback_result = $event->getControllerResult();
-      $event->setResponse(new Response(drupal_render_page($page_callback_result)));
-    }
-    else {
-      $event->setResponse(new Response('Unsupported Media Type', 415));
-    }
-  }
-
-  /**
-   * Registers the methods in this class that should be listeners.
-   *
-   * @return array
-   *   An array of event listener definitions.
-   */
-  static function getSubscribedEvents() {
-    // Since we want HTML to be our default, catch-all response type, give its
-    // listeners a very low priority so that they always check last.
-
-    $events[KernelEvents::VIEW][] = array('onView', -5);
-
-    return $events;
-  }
-}
diff --git a/core/lib/Drupal/Core/EventSubscriber/JsonSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/JsonSubscriber.php
deleted file mode 100644
index cc64290cc7bbab30d1c5c9c50a20ce7d534ef9f6..0000000000000000000000000000000000000000
--- a/core/lib/Drupal/Core/EventSubscriber/JsonSubscriber.php
+++ /dev/null
@@ -1,76 +0,0 @@
-<?php
-
-namespace Drupal\Core\EventSubscriber;
-
-use Symfony\Component\HttpFoundation\Response;
-use Symfony\Component\HttpKernel\KernelEvents;
-use Symfony\Component\HttpKernel\Event\GetResponseEvent;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
-use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
-use Symfony\Component\Routing\Exception\ResourceNotFoundException;
-use Symfony\Component\Routing\Exception\MethodNotAllowedException;
-use Symfony\Component\EventDispatcher\EventSubscriberInterface;
-
-/**
- * @file
- *
- * Definition of Drupal\Core\EventSubscriber\HtmlSubscriber;
- */
-
-/**
- * Main subscriber for JSON-type HTTP responses.
- */
-class JsonSubscriber implements EventSubscriberInterface {
-
-  /**
-   * Determines if we are dealing with an JSON-style response.
-   *
-   * @param GetResponseEvent $event
-   *   The Event to process.
-   * @return boolean
-   *   True if it is an event we should process as JSON, False otherwise.
-   */
-  protected function isJsonRequestEvent(GetResponseEvent $event) {
-    return in_array('application/json', $event->getRequest()->getAcceptableContentTypes());
-  }
-
-
-  /**
-   * Processes a successful controller into an HTTP 200 response.
-   *
-   * Some controllers may not return a response object but simply the body of
-   * one.  The VIEW event is called in that case, to allow us to mutate that
-   * body into a Response object.  In particular we assume that the return
-   * from an JSON-type response is a JSON string, so just wrap it into a
-   * Response object.
-   *
-   * @param GetResponseEvent $event
-   *   The Event to process.
-   */
-  public function onView(GetResponseEvent $event) {
-    if ($this->isJsonRequestEvent($event)) {
-      $page_callback_result = $event->getControllerResult();
-
-      $response = $this->createJsonResponse();
-      $response->setContent($page_callback_result);
-
-      $event->setResponse($response);
-    }
-  }
-
-  /**
-   * Registers the methods in this class that should be listeners.
-   *
-   * @return array
-   *   An array of event listener definitions.
-   */
-  static function getSubscribedEvents() {
-    //$events[KernelEvents::EXCEPTION][] = array('onNotFoundHttpException');
-    //$events[KernelEvents::EXCEPTION][] = array('onAccessDeniedException');
-    //$events[KernelEvents::EXCEPTION][] = array('onMethodAllowedException');
-
-    $events[KernelEvents::VIEW][] = array('onView');
-
-    return $events;
-  }
-}
diff --git a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
new file mode 100644
index 0000000000000000000000000000000000000000..8ade061551a526456eaa1e30c9b13165d7063263
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace Drupal\Core\EventSubscriber;
+
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+use Drupal\Core\ContentNegotiation;
+
+/**
+ * @file
+ *
+ * Definition of Drupal\Core\EventSubscriber\ViewSubscriber;
+ */
+
+/**
+ * Main subscriber for VIEW HTTP responses.
+ */
+class ViewSubscriber implements EventSubscriberInterface {
+
+  protected $negotiation;
+
+  public function __construct(ContentNegotiation $negotiation) {
+    $this->negotiation = $negotiation;
+  }
+
+  protected function createJsonResponse() {
+    $response = new Response();
+    $response->headers->set('Content-Type', 'application/json; charset=utf-8');
+
+    return $response;
+  }
+
+
+  /**
+   * Processes a successful controller into an HTTP 200 response.
+   *
+   * Some controllers may not return a response object but simply the body of
+   * one.  The VIEW event is called in that case, to allow us to mutate that
+   * body into a Response object.  In particular we assume that the return
+   * from an JSON-type response is a JSON string, so just wrap it into a
+   * Response object.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onView(GetResponseEvent $event) {
+
+    $request = $event->getRequest();
+
+    $method = 'on' . $this->negotiation->getContentType($request);
+
+    if (method_exists($this, $method)) {
+      $event->setResponse($this->$method($event));
+    }
+    else {
+      $event->setResponse(new Response('Unsupported Media Type', 415));
+    }
+  }
+
+  public function onJson(GetResponseEvent $event) {
+    $page_callback_result = $event->getControllerResult();
+
+    print_r($page_callback_result);
+
+    $response = $this->createJsonResponse();
+    $response->setContent($page_callback_result);
+
+    return $response;
+  }
+
+  /**
+   * Processes a successful controller into an HTTP 200 response.
+   *
+   * Some controllers may not return a response object but simply the body of
+   * one.  The VIEW event is called in that case, to allow us to mutate that
+   * body into a Response object.  In particular we assume that the return
+   * from an HTML-type response is a render array from a legacy page callback
+   * and render it.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onHtml(GetResponseEvent $event) {
+    $page_callback_result = $event->getControllerResult();
+    return new Response(drupal_render_page($page_callback_result));
+  }
+
+  /**
+   * Registers the methods in this class that should be listeners.
+   *
+   * @return array
+   *   An array of event listener definitions.
+   */
+  static function getSubscribedEvents() {
+    $events[KernelEvents::VIEW][] = array('onView');
+
+    return $events;
+  }
+}
diff --git a/core/lib/Drupal/Core/ExceptionController.php b/core/lib/Drupal/Core/ExceptionController.php
new file mode 100644
index 0000000000000000000000000000000000000000..5c867c727bc363bb0e9e61c513a44508f135e58a
--- /dev/null
+++ b/core/lib/Drupal/Core/ExceptionController.php
@@ -0,0 +1,141 @@
+<?php
+
+
+namespace Drupal\Core;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\HttpKernel\Exception\FlattenException;
+
+use Exception;
+
+/**
+ * Description of ExceptionController
+ *
+ */
+class ExceptionController {
+
+  protected $negotiation;
+
+  public function __construct(ContentNegotiation $negotiation) {
+    $this->negotiation = $negotiation;
+  }
+
+  public function execute(FlattenException $exception, Request $request) {
+
+    $method = 'on' . $exception->getStatusCode() . $this->negotiation->getContentType($request);
+
+    if (method_exists($this, $method)) {
+      return $this->$method($exception, $request);
+    }
+
+    return new Response('A fatal error occurred: ' . $exception->getMessage(), $exception->getStatusCode());
+
+  }
+
+  /**
+   * Processes a MethodNotAllowed exception into an HTTP 405 response.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function on405Html(FlattenException $exception, Request $request) {
+    $event->setResponse(new Response('Method Not Allowed', 405));
+  }
+
+  /**
+   * Processes an AccessDenied exception into an HTTP 403 response.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function on403Html(FlattenException $exception, Request $request) {
+    return new Response('Access Denied', 403);
+  }
+
+  public function on404Html(FlattenException $exception, Request $request) {
+    watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
+
+    // Check for and return a fast 404 page if configured.
+    // @todo Inline this rather than using a function.
+    drupal_fast_404();
+
+    $system_path = $request->attributes->get('system_path');
+
+    // Keep old path for reference, and to allow forms to redirect to it.
+    if (!isset($_GET['destination'])) {
+      $_GET['destination'] = $system_path;
+    }
+
+    $path = drupal_get_normal_path(variable_get('site_404', ''));
+    if ($path && $path != $system_path) {
+      // @TODO: Um, how do I specify an override URL again? Totally not clear.
+      // Do that and sub-call the kernel rather than using meah().
+      $request = Request::create($path);
+
+      $kernel = new DrupalKernel();
+      $response = $kernel->handle($request, HttpKernelInterface::SUB_REQUEST);
+      $response->setStatusCode(404, 'Not Found');
+    }
+    else {
+      $response = new Response('Not Found', 404);
+
+      // @todo Replace this block with something cleaner.
+      $return = t('The requested page "@path" could not be found.', array('@path' => $request->getPathInfo()));
+      drupal_set_title(t('Page not found'));
+      drupal_set_page_content($return);
+      $page = element_info('page');
+      $content = drupal_render_page($page);
+
+      $response->setContent($content);
+    }
+
+    return $response;
+  }
+
+  protected function createJsonResponse() {
+    $response = new Response();
+    $response->headers->set('Content-Type', 'application/json; charset=utf-8');
+
+    return $response;
+  }
+
+  /**
+   * Processes an AccessDenied exception into an HTTP 403 response.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function on403Json(FlattenException $exception, Request $request) {
+    $response = $this->createJsonResponse();
+    $response->setStatusCode(403, 'Access Denied');
+    return $response;
+  }
+
+  /**
+   * Processes a NotFound exception into an HTTP 404 response.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function on404Json(FlattenException $exception, Request $request) {
+    $response = $this->createJsonResponse();
+    $response->setStatusCode(404, 'Not Found');
+    return $response;
+  }
+
+  /**
+   * Processes a MethodNotAllowed exception into an HTTP 405 response.
+   *
+   * @param GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function on405Json(FlattenException $exception, Request $request) {
+    $response = $this->createJsonResponse();
+    $response->setStatusCode(405, 'Method Not Allowed');
+    return $response;
+  }
+
+}