diff --git a/core/core.services.yml b/core/core.services.yml
index 0f4d586..83db886 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -161,7 +161,7 @@ services:
     arguments: [default]
   form_builder:
     class: Drupal\Core\Form\FormBuilder
-    arguments: ['@form_validator', '@form_submitter', '@form_cache', '@module_handler', '@event_dispatcher', '@request_stack', '@class_resolver', '@theme.manager', '@?csrf_token', '@?http_kernel']
+    arguments: ['@form_validator', '@form_submitter', '@form_cache', '@module_handler', '@event_dispatcher', '@request_stack', '@class_resolver', '@theme.manager', '@?csrf_token']
   form_validator:
     class: Drupal\Core\Form\FormValidator
     arguments: ['@request_stack', '@string_translation', '@csrf_token', '@logger.channel.form']
@@ -787,6 +787,10 @@ services:
     class: Drupal\Core\EventSubscriber\ExceptionTestSiteSubscriber
     tags:
       - { name: event_subscriber }
+  exception.enforced_form_response:
+    class: Drupal\Core\EventSubscriber\EnforcedFormResponseSubscriber
+    tags:
+      - { name: event_subscriber }
   route_processor_manager:
     class: Drupal\Core\RouteProcessor\RouteProcessorManager
     tags:
diff --git a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php
index 0df6a30..5029311 100644
--- a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\EventSubscriber;
 
+use Drupal\Core\Form\EnforcedResponse;
 use Drupal\Core\Page\HtmlFragment;
 use Drupal\Core\Page\HtmlFragmentRendererInterface;
 use Drupal\Core\Page\HtmlPageRendererInterface;
@@ -114,8 +115,21 @@ protected function createResponse($title, $body, $response_code) {
     $fragment = new HtmlFragment($body);
     $fragment->setTitle($title);
 
-    $page = $this->fragmentRenderer->render($fragment, $response_code);
-    return new Response($this->htmlPageRenderer->render($page), $page->getStatusCode());
+    // Normally the EnforcedFormResponseSubscriber takes care of the
+    // EnforcedResponseException. But outside of HttpKernel::handleRaw(), it is
+    // necessary to catch and handle it manually.
+    try {
+      $page = $this->fragmentRenderer->render($fragment, $response_code);
+      return new Response($this->htmlPageRenderer->render($page), $page->getStatusCode());
+    }
+    catch (\Exception $e) {
+      if ($response = EnforcedResponse::createFromException($e)) {
+        return $response;
+      }
+      else {
+        throw $e;
+      }
+    }
   }
 
 }
diff --git a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php
index e35eff4..78a9376 100644
--- a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php
@@ -11,6 +11,7 @@
 use Drupal\Component\Utility\String;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\ContentNegotiation;
+use Drupal\Core\Form\EnforcedResponse;
 use Drupal\Core\Page\DefaultHtmlPageRenderer;
 use Drupal\Core\Page\HtmlFragment;
 use Drupal\Core\Page\HtmlFragmentRendererInterface;
@@ -201,8 +202,21 @@ protected function createHtmlResponse($title, $body, $response_code) {
     $fragment = new HtmlFragment($body);
     $fragment->setTitle($title);
 
-    $page = $this->fragmentRenderer->render($fragment, $response_code);
-    return new Response($this->htmlPageRenderer->render($page), $page->getStatusCode());
+    // Normally the EnforcedFormResponseSubscriber takes care of the
+    // EnforcedResponseException. But outside of HttpKernel::handleRaw(), it is
+    // necessary to catch and handle it manually.
+    try {
+      $page = $this->fragmentRenderer->render($fragment, $response_code);
+      return new Response($this->htmlPageRenderer->render($page), $page->getStatusCode());
+    }
+    catch (\Exception $e) {
+      if ($response = EnforcedResponse::createFromException($e)) {
+        return $response;
+      }
+      else {
+        throw $e;
+      }
+    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/EventSubscriber/EnforcedFormResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/EnforcedFormResponseSubscriber.php
new file mode 100644
index 0000000..b246f0f
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/EnforcedFormResponseSubscriber.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\EventSubscriber\EnforcedFormResponseSubscriber.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Drupal\Core\Form\EnforcedResponse;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\HttpKernel\KernelEvents;
+
+/**
+ * Handle the EnforcedResponseException and deliver an EnforcedResponse.
+ */
+class EnforcedFormResponseSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Replaces the response in case an EnforcedResponseException was thrown.
+   */
+  public function onKernelException(GetResponseForExceptionEvent $event) {
+    if ($response = EnforcedResponse::createFromException($event->getException())) {
+      // Setting the response stops the event propagation.
+      $event->setResponse($response);
+    }
+  }
+
+  /**
+   * Unwraps an enforced response.
+   */
+  public function onKernelResponse(FilterResponseEvent $event) {
+    $response = $event->getResponse();
+    if ($response instanceof EnforcedResponse && $event->getRequestType() === HttpKernelInterface::MASTER_REQUEST) {
+      $event->setResponse($response->getResponse());
+    }
+  }
+
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[KernelEvents::EXCEPTION] = array('onKernelException', 128);
+    $events[KernelEvents::RESPONSE] = array('onKernelResponse', 128);
+
+    return $events;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Form/EnforcedResponse.php b/core/lib/Drupal/Core/Form/EnforcedResponse.php
new file mode 100644
index 0000000..722d596
--- /dev/null
+++ b/core/lib/Drupal/Core/Form/EnforcedResponse.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Form\EnforcedResponse.
+ */
+
+namespace Drupal\Core\Form;
+
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * A wrapper containing a response which is to be enforced upon delivery.
+ *
+ * The FormBuilder throws an EnforcedResponseException whenever a form
+ * desires to explicitely set a response object. Exception handlers capable of
+ * setting the response should extract the response object of such an exception
+ * using EnforcedResponse::createFromException(). Then wrap it into an
+ * EnforcedResponse object and replace the original response with the wrapped
+ * response.
+ *
+ * @see Drupal\Core\EventSubscriber\EnforcedFormResponseSubscriber::onKernelException()
+ * @see Drupal\Core\EventSubscriber\DefaultExceptionSubscriber::createHtmlResponse()
+ * @see Drupal\Core\EventSubscriber\DefaultExceptionHtmlSubscriber::createResponse()
+ */
+class EnforcedResponse extends Response {
+
+  /**
+   * The wrapped response object.
+   *
+   * @var \Symfony\Component\HttpFoundation\Response;
+   */
+  protected $response;
+
+  /**
+   * Constructs a new enforced response from the given exception.
+   *
+   * Note that it is necessary to traverse the exception chain when searching
+   * for an enforced response. Otherwise it would be impossible to find an
+   * exception thrown from within a twig template.
+   *
+   * @param Exception $e
+   *   The exception where the enforced response is to be extracted from.
+   */
+  public static function createFromException(\Exception $e) {
+    while ($e) {
+      if ($e instanceof EnforcedResponseException) {
+        return new static($e->getResponse());
+      }
+
+      $e = $e->getPrevious();
+    }
+  }
+
+  /**
+   * Constructs an enforced response.
+   *
+   * Use EnforcedResponse::createFromException() instead.
+   *
+   * @param \Symfony\Component\HttpFoundation\Response $response
+   *   The response to wrap.
+   */
+  public function __construct(Response $response) {
+    parent::__construct('', 500);
+    $this->response = $response;
+  }
+
+  /**
+   * Returns the wrapped response.
+   *
+   * @return \Symfony\Component\HttpFoundation\Response
+   *   The wrapped response.
+   */
+  public function getResponse() {
+    return $this->response;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Form/EnforcedResponseException.php b/core/lib/Drupal/Core/Form/EnforcedResponseException.php
new file mode 100644
index 0000000..6ce5ca8
--- /dev/null
+++ b/core/lib/Drupal/Core/Form/EnforcedResponseException.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Form\EnforcedResponseException.
+ */
+
+namespace Drupal\Core\Form;
+
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Custom exception to break out of the main request and enforce a response.
+ */
+class EnforcedResponseException extends \Exception {
+
+  /**
+   * The response to be enforced.
+   *
+   * @var \Symfony\Component\HttpFoundation\Response
+   */
+  protected $response;
+
+  /**
+   * Constructs a new enforced response exception.
+   *
+   * @param \Symfony\Component\HttpFoundation\Response $response
+   *   The response to be enforced.
+   * @param string $message
+   *   (optional) The exception message.
+   * @param int $code
+   *   (optional) A user defined exception code.
+   * @param Exception $previous
+   *   (optional) The previous exception for nested exceptions
+   */
+  public function __construct(Response $response, $message = "", $code = 0, \Exception $previous = NULL) {
+    parent::__construct($message, $code, $previous);
+
+    $this->response = $response;
+  }
+
+  /**
+   * Return the response to be enforced.
+   *
+   * @returns \Symfony\Component\HttpFoundation\Response $response
+   *   The response to be enforced.
+   */
+  public function getResponse() {
+    return $this->response;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index 6fb6467..bbbf4ff 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -61,13 +61,6 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS
   protected $csrfToken;
 
   /**
-   * The HTTP kernel to handle forms returning response objects.
-   *
-   * @var \Symfony\Component\HttpKernel\HttpKernel
-   */
-  protected $httpKernel;
-
-  /**
    * The class resolver.
    *
    * @var \Drupal\Core\DependencyInjection\ClassResolverInterface
@@ -126,10 +119,8 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS
    *   The theme manager.
    * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
    *   The CSRF token generator.
-   * @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
-   *   The HTTP kernel.
    */
-  public function __construct(FormValidatorInterface $form_validator, FormSubmitterInterface $form_submitter, FormCacheInterface $form_cache, ModuleHandlerInterface $module_handler, EventDispatcherInterface $event_dispatcher, RequestStack $request_stack, ClassResolverInterface $class_resolver, ThemeManagerInterface $theme_manager, CsrfTokenGenerator $csrf_token = NULL, HttpKernelInterface $http_kernel = NULL) {
+  public function __construct(FormValidatorInterface $form_validator, FormSubmitterInterface $form_submitter, FormCacheInterface $form_cache, ModuleHandlerInterface $module_handler, EventDispatcherInterface $event_dispatcher, RequestStack $request_stack, ClassResolverInterface $class_resolver, ThemeManagerInterface $theme_manager, CsrfTokenGenerator $csrf_token = NULL) {
     $this->formValidator = $form_validator;
     $this->formSubmitter = $form_submitter;
     $this->formCache = $form_cache;
@@ -138,7 +129,6 @@ public function __construct(FormValidatorInterface $form_validator, FormSubmitte
     $this->requestStack = $request_stack;
     $this->classResolver = $class_resolver;
     $this->csrfToken = $csrf_token;
-    $this->httpKernel = $http_kernel;
     $this->themeManager = $theme_manager;
   }
 
@@ -260,10 +250,11 @@ public function buildForm($form_id, FormStateInterface &$form_state) {
     // can use it to know or update information about the state of the form.
     $response = $this->processForm($form_id, $form, $form_state);
 
-    // If the form returns some kind of response, deliver it.
+    // If the form returns a response, skip subsequent page construction by
+    // throwing an exception.
+    // @see Drupal\Core\EventSubscriber\EnforcedFormResponseSubscriber
     if ($response instanceof Response) {
-      $this->sendResponse($response);
-      exit;
+      throw new EnforcedResponseException($response);
     }
 
     // If this was a successful submission of a single-step form or the last step
@@ -403,10 +394,11 @@ public function retrieveForm($form_id, FormStateInterface &$form_state) {
     $args = array_merge(array($form, &$form_state), $args);
 
     $form = call_user_func_array($callback, $args);
-    // If the form returns some kind of response, deliver it.
+    // If the form returns a response, skip subsequent page construction by
+    // throwing an exception.
+    // @see Drupal\Core\EventSubscriber\EnforcedFormResponseSubscriber
     if ($form instanceof Response) {
-      $this->sendResponse($form);
-      exit;
+      throw new EnforcedResponseException($response);
     }
     $form['#form_id'] = $form_id;
 
@@ -1068,24 +1060,6 @@ protected function buttonWasClicked($element, FormStateInterface &$form_state) {
   }
 
   /**
-   * Triggers kernel.response and sends a form response.
-   *
-   * @param \Symfony\Component\HttpFoundation\Response $response
-   *   A response object.
-   */
-  protected function sendResponse(Response $response) {
-    $request = $this->requestStack->getCurrentRequest();
-    $event = new FilterResponseEvent($this->httpKernel, $request, HttpKernelInterface::MASTER_REQUEST, $response);
-
-    $this->eventDispatcher->dispatch(KernelEvents::RESPONSE, $event);
-    // Prepare and send the response.
-    $event->getResponse()
-      ->prepare($request)
-      ->send();
-    $this->httpKernel->terminate($request, $response);
-  }
-
-  /**
    * Wraps element_info().
    *
    * @return array
diff --git a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
index 9e36fcc..854ea06 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\Tests\Core\Form {
 
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Form\EnforcedResponseException;
 use Drupal\Core\Form\FormInterface;
 use Drupal\Core\Form\FormState;
 use Drupal\Core\Form\FormStateInterface;
@@ -115,9 +116,6 @@ public function testHandleFormStateResponse($class, $form_state_key) {
     $response = $this->getMockBuilder($class)
       ->disableOriginalConstructor()
       ->getMock();
-    $response->expects($this->any())
-      ->method('prepare')
-      ->will($this->returnValue($response));
 
     $form_arg = $this->getMockForm($form_id, $expected_form);
     $form_arg->expects($this->any())
@@ -131,12 +129,12 @@ public function testHandleFormStateResponse($class, $form_state_key) {
       $input['form_id'] = $form_id;
       $form_state->setUserInput($input);
       $this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE);
-      $this->fail('TestFormBuilder::sendResponse() was not triggered.');
+      $this->fail('EnforcedResponseException was not thrown.');
     }
-    catch (\Exception $e) {
-      $this->assertSame('exit', $e->getMessage());
+    catch (EnforcedResponseException $e) {
+      $this->assertSame($response, $e->getResponse());
     }
-    $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $form_state->getResponse());
+    $this->assertSame($response, $form_state->getResponse());
   }
 
   /**
@@ -160,16 +158,11 @@ public function testHandleRedirectWithResponse() {
     $response = $this->getMockBuilder('Symfony\Component\HttpFoundation\Response')
       ->disableOriginalConstructor()
       ->getMock();
-    $response->expects($this->once())
-      ->method('prepare')
-      ->will($this->returnValue($response));
 
     // Set up a redirect that will not be called.
     $redirect = $this->getMockBuilder('Symfony\Component\HttpFoundation\RedirectResponse')
       ->disableOriginalConstructor()
       ->getMock();
-    $redirect->expects($this->never())
-      ->method('prepare');
 
     $form_arg = $this->getMockForm($form_id, $expected_form);
     $form_arg->expects($this->any())
@@ -185,10 +178,10 @@ public function testHandleRedirectWithResponse() {
       $input['form_id'] = $form_id;
       $form_state->setUserInput($input);
       $this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE);
-      $this->fail('TestFormBuilder::sendResponse() was not triggered.');
+      $this->fail('EnforcedResponseException was not thrown.');
     }
-    catch (\Exception $e) {
-      $this->assertSame('exit', $e->getMessage());
+    catch (EnforcedResponseException $e) {
+      $this->assertSame($response, $e->getResponse());
     }
     $this->assertSame($response, $form_state->getResponse());
   }
@@ -370,9 +363,6 @@ public function testSendResponse() {
     $expected_form = $this->getMockBuilder('Symfony\Component\HttpFoundation\Response')
       ->disableOriginalConstructor()
       ->getMock();
-    $expected_form->expects($this->once())
-      ->method('prepare')
-      ->will($this->returnValue($expected_form));
 
     $form_arg = $this->getMockForm($form_id, $expected_form);
 
