diff --git a/core/includes/batch.inc b/core/includes/batch.inc
index ff0b4cb..aacc1b4 100644
--- a/core/includes/batch.inc
+++ b/core/includes/batch.inc
@@ -14,7 +14,8 @@
  * @see batch_get()
  */
 
-use \Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 
 /**
  * Loads a batch from the database.
@@ -54,7 +55,7 @@ function _batch_page() {
     $batch = batch_load($_REQUEST['id']);
     if (!$batch) {
       drupal_set_message(t('No active batch.'), 'error');
-      drupal_goto();
+      return new RedirectResponse('<front>');
     }
   }
 
@@ -469,6 +470,9 @@ function _batch_finished() {
 
     // Use drupal_redirect_form() to handle the redirection logic.
     drupal_redirect_form($_batch['form_state']);
+    if ($redirect = drupal_set_redirect()) {
+      return $redirect;
+    }
 
     // If no redirection happened, redirect to the originating page. In case the
     // form needs to be rebuilt, save the final $form_state for
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 4711c8d..bf13691 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -14,6 +14,7 @@
 use Symfony\Component\DependencyInjection\Reference;
 use Symfony\Component\DependencyInjection\Exception\RuntimeException as DependencyInjectionRuntimeException;
 use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Lock\DatabaseLockBackend;
 use Drupal\Core\Lock\LockBackendInterface;
@@ -3401,6 +3402,22 @@ function drupal_check_memory_limit($required, $memory_limit = NULL) {
 }
 
 /**
+ * Set a redirect response.
+ *
+ * This should only be used if there is no other way to return the response
+ * as the page callback.
+ *
+ * @param \Symfony\Component\HttpFoundation\RedirectResponse $response
+ */
+function drupal_set_redirect(RedirectResponse $response = NULL) {
+  $stored_response = &drupal_static(__FUNCTION__);
+  if ($response) {
+    $stored_response = $response;
+  }
+  return $stored_response;
+}
+
+/**
  * @defgroup lock Locking mechanisms
  * @{
  * Functions to coordinate long-running operations across requests.
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 0e824a0..d4b70da 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -541,7 +541,7 @@ function drupal_http_build_query(array $query, $parent = '') {
 }
 
 /**
- * Prepares a 'destination' URL query parameter for use with drupal_goto().
+ * Prepares a 'destination' URL query parameter for use with RedirectResponse.
  *
  * Used to direct the user back to the referring page after completing a form.
  * By default the current URL is returned. If a destination exists in the
@@ -554,7 +554,7 @@ function drupal_http_build_query(array $query, $parent = '') {
  *     not available, the current path.
  *
  * @see current_path()
- * @see drupal_goto()
+ * @see \Symfony\Component\HttpFoundation\RedirectResponse
  */
 function drupal_get_destination() {
   $destination = &drupal_static(__FUNCTION__);
@@ -608,7 +608,6 @@ function drupal_get_destination() {
  *   - 'fragment': The fragment of $url, if existent.
  *
  * @see url()
- * @see drupal_goto()
  * @ingroup php_wrappers
  */
 function drupal_parse_url($url) {
@@ -668,78 +667,6 @@ function drupal_encode_path($path) {
 }
 
 /**
- * Sends the user to a different Drupal page.
- *
- * This issues an on-site HTTP redirect. The function makes sure the redirected
- * URL is formatted correctly.
- *
- * If a destination was specified in the current request's URI (i.e.,
- * $_GET['destination']) then it will override the $path and $options values
- * passed to this function. This provides the flexibility to build a link to
- * user/login and override the default redirection so that the user is
- * redirected to a specific path after logging in:
- * @code
- *   $query = array('destination' => "node/$node->nid");
- *   $link = l(t('Log in'), 'user/login', array('query' => $query));
- * @endcode
- *
- * Drupal will ensure that messages set by drupal_set_message() and other
- * session data are written to the database before the user is redirected.
- *
- * This function ends the request; use it instead of a return in your menu
- * callback.
- *
- * @param $path
- *   (optional) A Drupal path or a full URL, which will be passed to url() to
- *   compute the redirect for the URL.
- * @param $options
- *   (optional) An associative array of additional URL options to pass to url().
- * @param $http_response_code
- *   (optional) The HTTP status code to use for the redirection, defaults to
- *   302. The valid values for 3xx redirection status codes are defined in
- *   @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3 RFC 2616 @endlink
- *   and the
- *   @link http://tools.ietf.org/html/draft-reschke-http-status-308-07 draft for the new HTTP status codes: @endlink
- *   - 301: Moved Permanently (the recommended value for most redirects).
- *   - 302: Found (default in Drupal and PHP, sometimes used for spamming search
- *     engines).
- *   - 303: See Other.
- *   - 304: Not Modified.
- *   - 305: Use Proxy.
- *   - 307: Temporary Redirect.
- *
- * @see drupal_get_destination()
- * @see url()
- */
-function drupal_goto($path = '', array $options = array(), $http_response_code = 302) {
-  // A destination in $_GET always overrides the function arguments.
-  // We do not allow absolute URLs to be passed via $_GET, as this can be an
-  // attack vector, with the following exception:
-  // - Absolute URLs that point to this site (i.e. same base URL and
-  //   base path) are allowed.
-  if (isset($_GET['destination']) && (!url_is_external($_GET['destination']) || _external_url_is_local($_GET['destination']))) {
-    $destination = drupal_parse_url($_GET['destination']);
-    $path = $destination['path'];
-    $options['query'] = $destination['query'];
-    $options['fragment'] = $destination['fragment'];
-  }
-
-  drupal_alter('drupal_goto', $path, $options, $http_response_code);
-
-  // The 'Location' HTTP header must be absolute.
-  $options['absolute'] = TRUE;
-
-  $url = url($path, $options);
-
-  header('Location: ' . $url, TRUE, $http_response_code);
-
-  // The "Location" header sends a redirect status code to the HTTP daemon. In
-  // some cases this can be wrong, so we make sure none of the code below the
-  // drupal_goto() call gets executed upon redirection.
-  drupal_exit($url);
-}
-
-/**
  * Determines if an external URL points to this Drupal installation.
  *
  * @param $url
@@ -2322,16 +2249,11 @@ function l($text, $path, array $options = array()) {
  * Performs end-of-request tasks.
  *
  * There should rarely be a reason to call exit instead of drupal_exit();
- *
- * @param $destination
- *   If this function is called from drupal_goto(), then this argument
- *   will be a fully-qualified URL that is the destination of the redirect.
- *   This should be passed along to hook_exit() implementations.
  */
-function drupal_exit($destination = NULL) {
+function drupal_exit() {
   if (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL) {
     if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
-      module_invoke_all('exit', $destination);
+      module_invoke_all('exit');
     }
     drupal_session_commit();
   }
diff --git a/core/includes/form.inc b/core/includes/form.inc
index ca9e614..12f5dc2 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -5,6 +5,7 @@
  * Functions for form and batch generation and processing.
  */
 
+use Symfony\Component\HttpFoundation\RedirectResponse;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Form\FormInterface;
 use Drupal\Core\Database\Database;
@@ -222,9 +223,9 @@ function drupal_get_form($form_arg) {
  *     errors.
  *   - redirect: Used to redirect the form on submission. It may either be a
  *     string containing the destination URL, or an array of arguments
- *     compatible with drupal_goto(). See drupal_redirect_form() for complete
+ *     compatible with RedirectResponse. See drupal_redirect_form() for complete
  *     information.
- *   - no_redirect: If set to TRUE the form will NOT perform a drupal_goto(),
+ *   - no_redirect: If set to TRUE the form will NOT return a RedirectResponse,
  *     even if 'redirect' is set.
  *   - method: The HTTP form method to use for finding the input for this form.
  *     May be 'post' or 'get'. Defaults to 'post'. Note that 'get' method
@@ -407,7 +408,10 @@ function drupal_build_form($form_id, &$form_state) {
   //   appropriate information persists to the next page request.
   // All of the handlers in the pipeline receive $form_state by reference and
   // can use it to know or update information about the state of the form.
-  drupal_process_form($form_id, $form, $form_state);
+  $response = drupal_process_form($form_id, $form, $form_state);
+  if (is_object($response)) {
+    return $response;
+  }
 
   // If this was a successful submission of a single-step form or the last step
   // of a multi-step form, then drupal_process_form() issued a redirect to
@@ -934,7 +938,7 @@ function drupal_process_form($form_id, &$form, &$form_state) {
         }
 
         $batch['progressive'] = !$form_state['programmed'];
-        batch_process();
+        return batch_process();
 
         // Execution continues only for programmatic forms.
         // For 'regular' forms, we get redirected to the batch processing
@@ -947,6 +951,7 @@ function drupal_process_form($form_id, &$form, &$form_state) {
 
       // Redirect the form based on values in $form_state.
       drupal_redirect_form($form_state);
+      return;
     }
 
     // Don't rebuild or cache form submissions invoked via drupal_form_submit().
@@ -1238,7 +1243,7 @@ function drupal_validate_form($form_id, &$form, &$form_state) {
  *
  * Usually (for exceptions, see below) $form_state['redirect'] determines where
  * to redirect the user. This can be set either to a string (the path to
- * redirect to), or an array of arguments for drupal_goto(). If
+ * redirect to), or an array of arguments for RedirectResponse. If
  * $form_state['redirect'] is missing, the user is usually (again, see below for
  * exceptions) redirected back to the page they came from, where they should see
  * a fresh, unpopulated copy of the form.
@@ -1272,12 +1277,12 @@ function drupal_validate_form($form_id, &$form, &$form_state) {
  *   form builder functions or form validation/submit handlers.
  * - If $form_state['redirect'] is set to FALSE, redirection is disabled.
  * - If none of the above conditions has prevented redirection, then the
- *   redirect is accomplished by calling drupal_goto(), passing in the value of
- *   $form_state['redirect'] if it is set, or the current path if it is
- *   not. drupal_goto() preferentially uses the value of $_GET['destination']
+ *   redirect is accomplished by returning a RedirectResponse, passing in the
+ *   value of $form_state['redirect'] if it is set, or the current path if it is
+ *   not. RedirectResponse preferentially uses the value of $_GET['destination']
  *   (the 'destination' URL query string) if it is present, so this will
  *   override any values set by $form_state['redirect']. Note that during
- *   installation, install_goto() is called in place of drupal_goto().
+ *   installation, install_goto() is called in place of RedirectResponse.
  *
  * @param $form_state
  *   An associative array containing the current state of the form.
@@ -1298,21 +1303,31 @@ function drupal_redirect_form($form_state) {
   if (!empty($form_state['no_redirect'])) {
     return;
   }
-  // Only invoke drupal_goto() if redirect value was not set to FALSE.
+  // Only return a RedirectResponse if redirect value was not set to FALSE.
   if (!isset($form_state['redirect']) || $form_state['redirect'] !== FALSE) {
     if (isset($form_state['redirect'])) {
       if (is_array($form_state['redirect'])) {
-        call_user_func_array('drupal_goto', $form_state['redirect']);
+        drupal_set_redirect(new RedirectResponse(url($form_state['redirect'][0], $form_state['redirect'][1]), isset($form_state['redirect'][2]) ? $form_state['redirect'][2] : 302));
+        return;
       }
       else {
         // This function can be called from the installer, which guarantees
         // that $redirect will always be a string, so catch that case here
         // and use the appropriate redirect function.
-        $function = drupal_installation_attempted() ? 'install_goto' : 'drupal_goto';
-        $function($form_state['redirect']);
+        if (drupal_installation_attempted()) {
+          install_goto($form_state['redirect']);
+        }
+        else {
+          drupal_set_redirect(new RedirectResponse($form_state['redirect'], 302));
+          return;
+        }
       }
     }
-    drupal_goto(current_path(), array('query' => drupal_container()->get('request')->query->all()));
+    $url = url(ltrim(Drupal::service('request')->getPathInfo(), '/'), array(
+      'query' => Drupal::service('request')->query->all()
+    ));
+    drupal_set_redirect(new RedirectResponse($url));
+    return;
   }
 }
 
@@ -5055,7 +5070,7 @@ function batch_set($batch_definition) {
  * Processes the batch.
  *
  * Unless the batch has been marked with 'progressive' = FALSE, the function
- * issues a drupal_goto and thus ends page execution.
+ * returns a RedirectResponse.
  *
  * This function is generally not needed in form submit handlers;
  * Form API takes care of batches that were set during form submission.
@@ -5067,11 +5082,11 @@ function batch_set($batch_definition) {
  *   URL of the batch processing page.
  * @param $redirect_callback
  *   (optional) Specify a function to be called to redirect to the progressive
- *   processing page. By default drupal_goto() will be used to redirect to a
+ *   processing page. By default RedirectResponse will be used to redirect to a
  *   page which will do the progressive page. Specifying another function will
  *   allow the progressive processing to be processed differently.
  */
-function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = 'drupal_goto') {
+function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = NULL) {
   $batch =& batch_get();
 
   drupal_theme_initialize();
@@ -5112,8 +5127,8 @@ function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = 'd
       $t = get_t();
       $batch['error_message'] = $t('Please continue to <a href="@error_url">the error page</a>', array('@error_url' => url($url, array('query' => array('id' => $batch['id'], 'op' => 'finished')))));
 
-      // Clear the way for the drupal_goto() redirection to the batch processing
-      // page, by saving and unsetting the 'destination', if there is any.
+      // Clear the way for redirection to the batch processing page, by
+      // saving and unsetting the 'destination', if there is any.
       if (isset($_GET['destination'])) {
         $batch['destination'] = $_GET['destination'];
         unset($_GET['destination']);
@@ -5133,8 +5148,13 @@ function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = 'd
       $_SESSION['batches'][$batch['id']] = TRUE;
 
       // Redirect for processing.
-      $function = $batch['redirect_callback'];
-      $function($batch['url'], array('query' => array('op' => 'start', 'id' => $batch['id'])));
+      $options = array('query' => array('op' => 'start', 'id' => $batch['id']));
+      if (($function = $batch['redirect_callback']) && function_exists($function)) {
+        $function($batch['url'], $options);
+      }
+      else {
+        return new RedirectResponse(url($batch['url'], $options), 302);
+      }
     }
     else {
       // Non-progressive execution: bypass the whole progressbar workflow
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 47982dd..45b0030 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -588,7 +588,10 @@ function install_run_task($task, &$install_state) {
       }
       // Process the batch. For progressive batches, this will redirect.
       // Otherwise, the batch will complete.
-      batch_process(install_redirect_url($install_state), install_full_redirect_url($install_state));
+      $return = batch_process(install_redirect_url($install_state), install_full_redirect_url($install_state));
+      if (is_a($return, 'Symfony\Component\HttpFoundation\RedirectResponse')) {
+        install_goto($return->getTargetUrl());
+      }
     }
     // If we are in the middle of processing this batch, keep sending back
     // any output from the batch process, until the task is complete.
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 391750f..eef4095 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -861,7 +861,17 @@ function drupal_install_fix_file($file, $mask, $message = TRUE) {
 function install_goto($path) {
   global $base_url;
   include_once DRUPAL_ROOT . '/core/includes/common.inc';
-  header('Location: ' . $base_url . '/' . $path);
+
+  // TODO this is hacking around symfony's redirection giving us a fully
+  // qualified url. It's a quick hack to get things working but warrants a
+  // security review or a better implementation.
+  if (url_is_external($path)) {
+    $real_path = $path;
+  }
+  else {
+    $real_path = $base_url . '/' . $path;
+  }
+  header('Location: ' . $real_path);
   header('Cache-Control: no-cache'); // Not a permanent redirect.
   drupal_exit();
 }
diff --git a/core/includes/update.inc b/core/includes/update.inc
index 2719634..e0cd007 100644
--- a/core/includes/update.inc
+++ b/core/includes/update.inc
@@ -862,7 +862,7 @@ function update_do_one($module, $number, $dependency_map, &$context) {
  *
  * @see update_resolve_dependencies()
  */
-function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $redirect_callback = 'drupal_goto') {
+function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $redirect_callback = NULL) {
   // During the update, bring the site offline so that schema changes do not
   // affect visiting users.
   $maintenance_mode = config('system.maintenance')->get('enabled');
diff --git a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php
index 885db53..5554905 100644
--- a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\EventSubscriber;
 
 use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 use Symfony\Component\HttpKernel\KernelEvents;
 use Symfony\Component\HttpKernel\Event\GetResponseEvent;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -30,10 +31,14 @@ public function onKernelRequestMaintenanceModeCheck(GetResponseEvent $event) {
     // Allow other modules to change the site status but not the path. The path
     // can be changed using a request listener.
     $read_only_path = $event->getRequest()->attributes->get('system_path');
-    drupal_alter('menu_site_status', $status, $read_only_path);
+    $redirect_path = $read_only_path;
+    drupal_alter('menu_site_status', $status, $read_only_path, $redirect_path);
+    if ($redirect_path != $read_only_path) {
+      $event->setResponse(new RedirectResponse($redirect_path));
+    }
 
     // Only continue if the site is online.
-    if ($status != MENU_SITE_ONLINE) {
+    elseif ($status != MENU_SITE_ONLINE) {
       // Deliver the 503 page.
       drupal_maintenance_theme();
       drupal_set_title(t('Site under maintenance'));
diff --git a/core/lib/Drupal/Core/EventSubscriber/ResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ResponseSubscriber.php
new file mode 100644
index 0000000..cd13ddd
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/ResponseSubscriber.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\EventSubscriber\ResponseSubscriber.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Access subscriber for controller requests.
+ */
+class ResponseSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Allows manipulation of the response object when performing a redirect.
+   *
+   * @param Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
+   *   The Event to process.
+   */
+  public function onKernelResponse(FilterResponseEvent $event) {
+    $response = $event->getResponse();
+
+    if ($response instanceOf RedirectResponse) {
+      $options = array();
+
+      $request = $event->getRequest();
+      $destination = $request->query->get('destination');
+
+      // A destination in $_GET always overrides the function arguments.
+      // We do not allow absolute URLs to be passed via $_GET, as this can be an
+      // attack vector.
+      if (!empty($destination)) {
+        $destination = drupal_parse_url($destination);
+      }
+      else {
+        $destination = drupal_parse_url($response->getTargetUrl());
+      }
+
+      $target_path = $destination['path'];
+      $options['query'] = $destination['query'];
+      $options['fragment'] = $destination['fragment'];
+
+      // @todo: Use a custom redirect response for URL's with arguments?
+      if (is_array($target_path)) {
+        list($target_path, $options) = $target_path;
+      }
+      $options['absolute'] = TRUE;
+
+      // Remove leading /.
+      $target_path = ltrim($target_path, '/');
+
+      // @todo this should be replaced by the event subscriber pattern?
+      drupal_alter('redirect_response', $target_path, $options, $status);
+
+      $response->setTargetUrl(url($target_path, $options));
+    }
+  }
+
+  /**
+   * Registers the methods in this class that should be listeners.
+   *
+   * @return array
+   *   An array of event listener definitions.
+   */
+  static function getSubscribedEvents() {
+    $events[KernelEvents::RESPONSE][] = array('onKernelResponse', 100);
+    return $events;
+  }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
index ac48bc8..1488526 100644
--- a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
@@ -142,8 +142,19 @@ public function onIframeUpload(GetResponseForControllerResultEvent $event) {
    */
   public function onHtml(GetResponseForControllerResultEvent $event) {
     $page_callback_result = $event->getControllerResult();
-    return new Response(drupal_render_page($page_callback_result));
-  }
+    // Handle early global redirects.
+    if ($redirect = drupal_set_redirect()) {
+      return $redirect;
+    }
+    $page = drupal_render_page($page_callback_result);
+    // Check for redirects that happened late, e.g. during forms built in
+    // preprocess functions.
+    // Handle global redirects.
+    if ($redirect = drupal_set_redirect()) {
+      return $redirect;
+    }
+    return new Response($page);
+    }
 
   /**
    * Registers the methods in this class that should be listeners.
diff --git a/core/modules/action/action.admin.inc b/core/modules/action/action.admin.inc
index 4ce7418..c86db3f 100644
--- a/core/modules/action/action.admin.inc
+++ b/core/modules/action/action.admin.inc
@@ -5,6 +5,8 @@
  * Admin page callbacks for the Action module.
  */
 
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
 /**
  * Creates the form for confirmation of deleting an action.
  *
@@ -48,4 +50,3 @@ function action_admin_delete_orphans_post($orphaned) {
     drupal_set_message(t("Deleted orphaned action (%action).", array('%action' => $callback)));
   }
 }
-
diff --git a/core/modules/action/action.module b/core/modules/action/action.module
index 24436ea..a839d36 100644
--- a/core/modules/action/action.module
+++ b/core/modules/action/action.module
@@ -23,6 +23,8 @@
  * @} End of "defgroup actions".
  */
 
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
 /**
  * Implements hook_help().
  */
@@ -694,5 +696,5 @@ function action_goto_action_submit($form, $form_state) {
  * @ingroup actions.
  */
 function action_goto_action($entity, $context) {
-  drupal_goto(token_replace($context['url'], $context));
+  return new RedirectResponse(token_replace($context['url'], 302, $context));
 }
diff --git a/core/modules/action/lib/Drupal/action/Form/ActionAdminConfigureForm.php b/core/modules/action/lib/Drupal/action/Form/ActionAdminConfigureForm.php
index 8c83491..c93581b 100644
--- a/core/modules/action/lib/Drupal/action/Form/ActionAdminConfigureForm.php
+++ b/core/modules/action/lib/Drupal/action/Form/ActionAdminConfigureForm.php
@@ -7,6 +7,7 @@
 namespace Drupal\action\Form;
 
 use Drupal\Core\Form\FormInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 
 /**
  * Provides a form for configuring an action.
@@ -25,7 +26,7 @@ public function getFormID() {
    */
   public function buildForm(array $form, array &$form_state, $action = NULL) {
     if ($action === NULL) {
-      drupal_goto('admin/config/system/actions');
+      return new RedirectResponse('admin/config/system/actions');
     }
 
     $actions_map = action_actions_map(action_list());
diff --git a/core/modules/aggregator/aggregator.admin.inc b/core/modules/aggregator/aggregator.admin.inc
index 6706f29..3097c23 100644
--- a/core/modules/aggregator/aggregator.admin.inc
+++ b/core/modules/aggregator/aggregator.admin.inc
@@ -6,6 +6,7 @@
  */
 
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 use Drupal\aggregator\Plugin\Core\Entity\Feed;
 use Guzzle\Http\Exception\RequestException;
 use Guzzle\Http\Exception\BadResponseException;
@@ -297,7 +298,7 @@ function aggregator_admin_refresh_feed(Feed $feed) {
   }
 
   aggregator_refresh($feed);
-  drupal_goto('admin/config/services/aggregator');
+  return new RedirectResponse('admin/config/services/aggregator');
 }
 
 /**
diff --git a/core/modules/aggregator/tests/aggregator_test.module b/core/modules/aggregator/tests/aggregator_test.module
index 09190fa..b5d5b9f 100644
--- a/core/modules/aggregator/tests/aggregator_test.module
+++ b/core/modules/aggregator/tests/aggregator_test.module
@@ -1,5 +1,7 @@
 <?php
 
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
 /**
  * Implements hook_menu().
  */
@@ -68,5 +70,5 @@ function aggregator_test_feed($use_last_modified = FALSE, $use_etag = FALSE) {
  * Page callback that redirects to another feed.
  */
 function aggregator_test_redirect() {
-  drupal_goto('aggregator/test-feed', array(), 301);
+  return new RedirectResponse('aggregator/test-feed', 301);
 }
diff --git a/core/modules/comment/comment.admin.inc b/core/modules/comment/comment.admin.inc
index 0418b1d..69b7dc7 100644
--- a/core/modules/comment/comment.admin.inc
+++ b/core/modules/comment/comment.admin.inc
@@ -7,6 +7,7 @@
 
 use Drupal\comment\Plugin\Core\Entity\Comment;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 
 /**
  * Page callback: Presents an administrative comment listing.
@@ -235,7 +236,7 @@ function comment_multiple_delete_confirm($form, &$form_state) {
 
   if (!$comment_counter) {
     drupal_set_message(t('There do not appear to be any comments to delete, or your selected comment was deleted by another administrator.'));
-    drupal_goto('admin/content/comment');
+    return new RedirectResponse('admin/content/comment');
   }
   else {
     return confirm_form($form,
diff --git a/core/modules/comment/comment.pages.inc b/core/modules/comment/comment.pages.inc
index be4ef3f..c95d2b9 100644
--- a/core/modules/comment/comment.pages.inc
+++ b/core/modules/comment/comment.pages.inc
@@ -8,6 +8,7 @@
 use Drupal\Core\Entity\EntityInterface;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 
 /**
  * Form constructor for the comment reply form.
@@ -47,7 +48,7 @@ function comment_reply(EntityInterface $node, $pid = NULL) {
     }
     else {
       drupal_set_message(t('You are not authorized to post comments.'), 'error');
-      drupal_goto("node/$node->nid");
+      RedirectResponse("node/$node->nid");
     }
   }
   else {
@@ -62,19 +63,19 @@ function comment_reply(EntityInterface $node, $pid = NULL) {
           if ($comment->nid->target_id != $node->nid) {
             // Attempting to reply to a comment not belonging to the current nid.
             drupal_set_message(t('The comment you are replying to does not exist.'), 'error');
-            drupal_goto("node/$node->nid");
+            RedirectResponse("node/$node->nid");
           }
           // Display the parent comment
           $build['comment_parent'] = comment_view($comment);
         }
         else {
           drupal_set_message(t('The comment you are replying to does not exist.'), 'error');
-          drupal_goto("node/$node->nid");
+          RedirectResponse("node/$node->nid");
         }
       }
       else {
         drupal_set_message(t('You are not authorized to view comments.'), 'error');
-        drupal_goto("node/$node->nid");
+        RedirectResponse("node/$node->nid");
       }
     }
     // This is the case where the comment is in response to a node. Display the node.
@@ -85,14 +86,14 @@ function comment_reply(EntityInterface $node, $pid = NULL) {
     // Should we show the reply box?
     if ($node->comment != COMMENT_NODE_OPEN) {
       drupal_set_message(t("This discussion is closed: you can't post new comments."), 'error');
-      drupal_goto("node/$node->nid");
+      RedirectResponse("node/$node->nid");
     }
     elseif (user_access('post comments')) {
       $build['comment_form'] = comment_add($node, $pid);
     }
     else {
       drupal_set_message(t('You are not authorized to post comments.'), 'error');
-      drupal_goto("node/$node->nid");
+      RedirectResponse("node/$node->nid");
     }
   }
 
@@ -121,7 +122,7 @@ function comment_approve($cid) {
     comment_save($comment);
 
     drupal_set_message(t('Comment approved.'));
-    drupal_goto('node/' . $comment->nid->target_id);
+    RedirectResponse('node/' . $comment->nid->target_id);
   }
   throw new NotFoundHttpException();
 }
diff --git a/core/modules/image/image.admin.inc b/core/modules/image/image.admin.inc
index df68065..7cee1a7 100644
--- a/core/modules/image/image.admin.inc
+++ b/core/modules/image/image.admin.inc
@@ -5,6 +5,8 @@
  * Administration pages for image settings.
  */
 
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
 /**
  * Menu callback; Listing of all current image styles.
  */
@@ -324,7 +326,7 @@ function image_effect_form($form, &$form_state, $style, $effect) {
   // If there's no configuration for this image effect, return to
   // the image style page.
   if (!isset($effect['form callback'])) {
-    drupal_goto('admin/config/media/image-styles/manage/' . $style->id());
+    return new RedirectResponse('admin/config/media/image-styles/manage/' . $style->id());
   }
   $form_state['image_style'] = $style;
   $form_state['image_effect'] = $effect;
diff --git a/core/modules/language/language.admin.inc b/core/modules/language/language.admin.inc
index f59fb38..5947fcf 100644
--- a/core/modules/language/language.admin.inc
+++ b/core/modules/language/language.admin.inc
@@ -7,6 +7,7 @@
 
 use Drupal\Core\Language\Language;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 
 /**
  * User interface for the language overview screen.
@@ -297,7 +298,7 @@ function language_admin_delete_form($form, &$form_state, $language) {
 
   if (language_default()->langcode == $langcode) {
     drupal_set_message(t('The default language cannot be deleted.'));
-    drupal_goto('admin/config/regional/language');
+    return new RedirectResponse('admin/config/regional/language');
   }
 
   // For other languages, warn the user that data loss is ahead.
diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc
index fc68a51..cb980ad 100644
--- a/core/modules/locale/locale.pages.inc
+++ b/core/modules/locale/locale.pages.inc
@@ -8,6 +8,7 @@
 use Drupal\locale\SourceString;
 use Drupal\locale\TranslationString;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 
 /**
  * Page callback: Shows the string search screen.
@@ -479,7 +480,7 @@ function locale_translation_manual_status() {
   if (batch_get()) {
     batch_process('admin/reports/translations');
   }
-  drupal_goto('admin/reports/translations');
+  return new RedirectResponse('admin/reports/translations');
 }
 
 /**
diff --git a/core/modules/node/node.pages.inc b/core/modules/node/node.pages.inc
index 863babd..dc4f54e 100644
--- a/core/modules/node/node.pages.inc
+++ b/core/modules/node/node.pages.inc
@@ -10,6 +10,7 @@
  */
 
 use Drupal\Core\Entity\EntityInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 
 /**
  * Page callback: Presents the node editing form.
@@ -50,7 +51,7 @@ function node_add_page() {
   // Bypass the node/add listing if only one content type is available.
   if (count($content) == 1) {
     $type = array_shift($content);
-    drupal_goto('node/add/' . $type->type);
+    RedirectResponse('node/add/' . $type->type);
   }
   return array('#theme' => 'node_add_list', '#content' => $content);
 }
diff --git a/core/modules/openid/openid.module b/core/modules/openid/openid.module
index 0f2e04f..cb5ee6b 100644
--- a/core/modules/openid/openid.module
+++ b/core/modules/openid/openid.module
@@ -6,6 +6,7 @@
  */
 
 use Guzzle\Http\Exception\RequestException;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 
 /**
  * Implements hook_menu().
@@ -777,7 +778,7 @@ function openid_authentication($response) {
     }
     else {
       module_invoke_all('openid_response', $response, $form_state['user']);
-      drupal_goto();
+      return new RedirectResponse();
     }
 
     $messages = drupal_get_messages('error');
@@ -799,13 +800,13 @@ function openid_authentication($response) {
     // registration page and prefill with the values we received.
     $destination = drupal_get_destination();
     unset($_GET['destination']);
-    drupal_goto('user/register', array('query' => $destination));
+    return new RedirectResponse(array('user/register', array('query' => $destination)));
   }
   else {
     drupal_set_message(t('Only site administrators can create new user accounts.'), 'error');
     module_invoke_all('openid_response', $response, NULL);
   }
-  drupal_goto();
+  return new RedirectResponse('<front>');
 }
 
 function openid_association_request($public) {
diff --git a/core/modules/openid/openid.pages.inc b/core/modules/openid/openid.pages.inc
index 81a5905..95655dc 100644
--- a/core/modules/openid/openid.pages.inc
+++ b/core/modules/openid/openid.pages.inc
@@ -5,6 +5,8 @@
  * User page callbacks for the openid module.
  */
 
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
 /**
  * Menu callback; Process an OpenID authentication.
  */
@@ -20,7 +22,7 @@ function openid_authentication_page() {
       drupal_set_message(t('OpenID login cancelled.'));
       break;
   }
-  drupal_goto();
+  return new RedirectResponse('<front>');
 }
 
 /**
diff --git a/core/modules/openid/tests/openid_test.module b/core/modules/openid/tests/openid_test.module
index c442884..adce934 100644
--- a/core/modules/openid/tests/openid_test.module
+++ b/core/modules/openid/tests/openid_test.module
@@ -311,7 +311,7 @@ function _openid_test_endpoint_authenticate() {
       'openid.mode' => 'error',
       'openid.error' => 'Unexpted identity',
     );
-    return new RedirectResponse(url($_REQUEST['openid_return_to'], array('query' => $response, 'external' => TRUE)));
+    return new RedirectResponse(array($_REQUEST['openid_return_to'], array('query' => $response, 'external' => TRUE)));
   }
 
   // Generate unique identifier for this authentication.
diff --git a/core/modules/overlay/overlay.module b/core/modules/overlay/overlay.module
index 446d56c..2af8079 100644
--- a/core/modules/overlay/overlay.module
+++ b/core/modules/overlay/overlay.module
@@ -7,6 +7,7 @@
 
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 
 /**
  * Implements hook_help().
@@ -146,7 +147,7 @@ function overlay_init() {
     // <front>#overlay=admin/modules to actually enable the overlay.
     if (isset($_SESSION['overlay_enable_redirect']) && $_SESSION['overlay_enable_redirect']) {
       unset($_SESSION['overlay_enable_redirect']);
-      drupal_goto('<front>', array('fragment' => 'overlay=' . $current_path));
+      return new RedirectResponse(array('<front>', array('fragment' => 'overlay=' . $current_path)));
     }
 
     if (isset($_GET['render']) && $_GET['render'] == 'overlay') {
@@ -253,9 +254,11 @@ function overlay_library_info() {
 }
 
 /**
- * Implements hook_drupal_goto_alter().
+ * Implements hook_redirect_response_alter().
+ *
+ * @todo convert this to an event subscriber?
  */
-function overlay_drupal_goto_alter(&$path, &$options, &$http_response_code) {
+function overlay_redirect_response_alter(&$path, &$options, &$http_response_code) {
   if (overlay_get_mode() == 'child') {
     // The authorize.php script bootstraps Drupal to a very low level, where
     // the PHP code that is necessary to close the overlay properly will not be
@@ -369,7 +372,7 @@ function overlay_user_dismiss_message() {
   drupal_container()->get('user.data')->set('overlay', $user->uid, 'message_dismissed', 1);
   drupal_set_message(t('The message has been dismissed. You can change your overlay settings at any time by visiting your profile page.'));
   // Destination is normally given. Go to the user profile as a fallback.
-  drupal_goto('user/' . $user->uid . '/edit');
+  return new RedirectResponse('user/' . $user->uid . '/edit');
 }
 
 /**
diff --git a/core/modules/search/search.pages.inc b/core/modules/search/search.pages.inc
index eaae71a..5c09c05 100644
--- a/core/modules/search/search.pages.inc
+++ b/core/modules/search/search.pages.inc
@@ -5,6 +5,8 @@
  * User page callbacks for the Search module.
  */
 
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
 /**
  * Page callback: Presents the search form and/or search results.
  *
@@ -39,7 +41,7 @@ function search_view($module = NULL, $keys = '') {
     if ($keys) {
       $path .= '/' . $keys;
     }
-    drupal_goto($path);
+    return new RedirectResponse($path);
   }
 
   // Default results output is an empty string.
diff --git a/core/modules/shortcut/shortcut.admin.inc b/core/modules/shortcut/shortcut.admin.inc
index 0e43052..aabe23e 100644
--- a/core/modules/shortcut/shortcut.admin.inc
+++ b/core/modules/shortcut/shortcut.admin.inc
@@ -6,6 +6,7 @@
  */
 
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 
 /**
  * Form callback: builds the form for switching shortcut sets.
@@ -601,7 +602,7 @@ function shortcut_link_add_inline($shortcut_set) {
     else {
       drupal_set_message(t('Unable to add a shortcut for %title.', array('%title' => $link['link_title'])));
     }
-    drupal_goto();
+    return new RedirectResponse('<front>');
   }
 
   throw new AccessDeniedHttpException();
diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module
index 5ad1b39..520f53d 100644
--- a/core/modules/simpletest/simpletest.module
+++ b/core/modules/simpletest/simpletest.module
@@ -54,8 +54,8 @@ function simpletest_menu() {
   );
   $items['admin/config/development/testing/results/%'] = array(
     'title' => 'Test result',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('simpletest_result_form', 5),
+    'page callback' => 'simpletest_test_result',
+    'page arguments' => array(5),
     'description' => 'View result of tests.',
     'access arguments' => array('administer unit tests'),
     'file' => 'simpletest.pages.inc',
diff --git a/core/modules/simpletest/simpletest.pages.inc b/core/modules/simpletest/simpletest.pages.inc
index 4fb908d..a4c31a3 100644
--- a/core/modules/simpletest/simpletest.pages.inc
+++ b/core/modules/simpletest/simpletest.pages.inc
@@ -5,6 +5,8 @@
  * Page callbacks for simpletest module.
  */
 
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
 /**
  * List tests arranged in groups that can be selected and run.
  */
@@ -204,16 +206,25 @@ function simpletest_test_form_submit($form, &$form_state) {
 }
 
 /**
- * Test results form for $test_id.
+ * Page callback for the rest results.
+ *
+ * @param string $test_id
+ *   The ID of the executed test.
  */
-function simpletest_result_form($form, &$form_state, $test_id) {
+function simpletest_test_result($test_id) {
   // Make sure there are test results to display and a re-run is not being performed.
-  $results = array();
   if (is_numeric($test_id) && !$results = simpletest_result_get($test_id)) {
     drupal_set_message(t('No test results to display.'), 'error');
-    drupal_goto('admin/config/development/testing');
-    return $form;
+    return new RedirectResponse('admin/config/development/testing');
   }
+  return drupal_get_form('simpletest_result_form', $test_id);
+}
+
+/**
+ * Test results form for $test_id.
+ */
+function simpletest_result_form($form, &$form_state, $test_id) {
+  $results = simpletest_result_get($test_id);
 
   // Load all classes and include CSS.
   drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css');
diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/GotoTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/GotoTest.php
index c9e3cfc..5507e96 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Common/GotoTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Common/GotoTest.php
@@ -8,9 +8,10 @@
 namespace Drupal\system\Tests\Common;
 
 use Drupal\simpletest\WebTestBase;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 
 /**
- * Tests drupal_goto() and hook_drupal_goto_alter().
+ * Tests RedirectResponse and hook_redirect_response_alter().
  */
 class GotoTest extends WebTestBase {
 
@@ -24,59 +25,59 @@ class GotoTest extends WebTestBase {
   public static function getInfo() {
     return array(
       'name' => 'Redirect functionality',
-      'description' => 'Tests the drupal_goto() and hook_drupal_goto_alter() functionality.',
+      'description' => 'Tests the RedirectResponse and hook_redirect_response_alter() functionality.',
       'group' => 'Common',
     );
   }
 
   /**
-   * Tests drupal_goto().
+   * Tests RedirectResponse.
    */
-  function testDrupalGoto() {
-    $this->drupalGet('common-test/drupal_goto/redirect');
+  function testRedirectResponse() {
+    $this->drupalGet('common-test/redirect-response/redirect');
     $headers = $this->drupalGetHeaders(TRUE);
     list(, $status) = explode(' ', $headers[0][':status'], 3);
     $this->assertEqual($status, 302, 'Expected response code was sent.');
-    $this->assertText('drupal_goto', 'Drupal goto redirect succeeded.');
-    $this->assertEqual($this->getUrl(), url('common-test/drupal_goto', array('absolute' => TRUE)), 'Drupal goto redirected to expected URL.');
+    $this->assertText('RedirectResponse', 'Drupal RedirectResponse redirect succeeded.');
+    $this->assertEqual($this->getUrl(), url('common-test/redirect-response', array('absolute' => TRUE)), 'Drupal RedirectResponse redirected to expected URL.');
 
-    $this->drupalGet('common-test/drupal_goto/redirect_advanced');
+    $this->drupalGet('common-test/redirect-response/redirect_advanced');
     $headers = $this->drupalGetHeaders(TRUE);
     list(, $status) = explode(' ', $headers[0][':status'], 3);
     $this->assertEqual($status, 301, 'Expected response code was sent.');
-    $this->assertText('drupal_goto', 'Drupal goto redirect succeeded.');
-    $this->assertEqual($this->getUrl(), url('common-test/drupal_goto', array('query' => array('foo' => '123'), 'absolute' => TRUE)), 'Drupal goto redirected to expected URL.');
+    $this->assertText('RedirectResponse', 'Drupal RedirectResponse redirect succeeded.');
+    $this->assertEqual($this->getUrl(), url('common-test/redirect-response', array('query' => array('foo' => '123'), 'absolute' => TRUE)), 'Drupal RedirectResponse redirected to expected URL.');
 
-    // Test that drupal_goto() respects ?destination=xxx. Use a complicated URL
+    // Test that RedirectResponse respects ?destination=xxx. Use a complicated URL
     // to test that the path is encoded and decoded properly.
-    $destination = 'common-test/drupal_goto/destination?foo=%2525&bar=123';
-    $this->drupalGet('common-test/drupal_goto/redirect', array('query' => array('destination' => $destination)));
-    $this->assertText('drupal_goto', 'Drupal goto redirect with destination succeeded.');
-    $this->assertEqual($this->getUrl(), url('common-test/drupal_goto/destination', array('query' => array('foo' => '%25', 'bar' => '123'), 'absolute' => TRUE)), 'Drupal goto redirected to given query string destination.');
+    $destination = 'common-test/redirect-response/destination?foo=%2525&bar=123';
+    $this->drupalGet('common-test/redirect-response/redirect', array('query' => array('destination' => $destination)));
+    $this->assertText('RedirectResponse', 'Drupal RedirectResponse redirect with destination succeeded.');
+    $this->assertEqual($this->getUrl(), url('common-test/redirect-response/destination', array('query' => array('foo' => '%25', 'bar' => '123'), 'absolute' => TRUE)), 'Drupal RedirectResponse redirected to given query string destination.');
 
-    // Test that drupal_goto() respects ?destination=xxx with an absolute URL
+    // Test that RedirectResponse respects ?destination=xxx with an absolute URL
     // that points to this Drupal installation.
-    $destination = url('common-test/drupal_goto/alternative', array('absolute' => TRUE));
-    $this->drupalGet('common-test/drupal_goto/redirect', array('query' => array('destination' => $destination)));
-    $this->assertText('drupal_goto_alternative', 'Drupal goto redirect with absolute URL destination that points to this Drupal installation succeeded.');
-    $this->assertEqual($this->getUrl(), url('common-test/drupal_goto/alternative', array('absolute' => TRUE)), 'Drupal goto redirected to given query string destination with absolute URL that points to this Drupal installation.');
+    $destination = url('common-test/redirect-response/alternative', array('absolute' => TRUE));
+    $this->drupalGet('common-test/redirect-response/redirect', array('query' => array('destination' => $destination)));
+    $this->assertText('RedirectResponse_alternative', 'Drupal RedirectResponse redirect with absolute URL destination that points to this Drupal installation succeeded.');
+    $this->assertEqual($this->getUrl(), url('common-test/redirect-response/alternative', array('absolute' => TRUE)), 'Drupal RedirectResponse redirected to given query string destination with absolute URL that points to this Drupal installation.');
 
-    // Test that drupal_goto() fails to respect ?destination=xxx with an absolute URL
+    // Test that RedirectResponse fails to respect ?destination=xxx with an absolute URL
     // that does not point to this Drupal installation.
     $destination = 'http://example.com';
-    $this->drupalGet('common-test/drupal_goto/redirect', array('query' => array('destination' => $destination)));
-    $this->assertText('drupal_goto', 'Drupal goto fails to redirect with absolute URL destination that does not point to this Drupal installation.');
-    $this->assertNotEqual($this->getUrl(), $destination, 'Drupal goto failed to redirect to given query string destination with absolute URL that does not point to this Drupal installation.');
+    $this->drupalGet('common-test/redirect-response/redirect', array('query' => array('destination' => $destination)));
+    $this->assertText('RedirectResponse', 'Drupal RedirectResponse fails to redirect with absolute URL destination that does not point to this Drupal installation.');
+    $this->assertNotEqual($this->getUrl(), $destination, 'Drupal RedirectResponse failed to redirect to given query string destination with absolute URL that does not point to this Drupal installation.');
   }
 
   /**
-   * Tests hook_drupal_goto_alter().
+   * Tests hook_redirect_response_alter().
    */
-  function testDrupalGotoAlter() {
-    $this->drupalGet('common-test/drupal_goto/redirect_fail');
+  function testDrupalRedirectResponseAlter() {
+    $this->drupalGet('common-test/redirect-response/redirect_fail');
 
-    $this->assertNoText(t("Drupal goto failed to stop program"), 'Drupal goto stopped program.');
-    $this->assertNoText('drupal_goto_fail', 'Drupal goto redirect failed.');
+    $this->assertNoText(t("Drupal RedirectResponse failed to stop program"), 'Drupal RedirectResponse stopped program.');
+    $this->assertNoText('RedirectResponse_fail', 'Drupal RedirectResponse redirect failed.');
   }
 
   /**
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index fde1bcc..1c88f74 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -9,6 +9,7 @@
 use Drupal\Core\Ajax\ReplaceCommand;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 use Drupal\Core\Datetime\DrupalDateTime;
 
@@ -300,7 +301,7 @@ function system_theme_enable() {
     else {
       drupal_set_message(t('The %theme theme was not found.', array('%theme' => $theme)), 'error');
     }
-    drupal_goto('admin/appearance');
+    return new RedirectResponse('admin/appearance');
   }
   throw new AccessDeniedHttpException();
 }
@@ -328,7 +329,7 @@ function system_theme_disable() {
     else {
       drupal_set_message(t('The %theme theme was not found.', array('%theme' => $theme)), 'error');
     }
-    drupal_goto('admin/appearance');
+    return new RedirectResponse('admin/appearance');
   }
   throw new AccessDeniedHttpException();
 }
@@ -377,7 +378,7 @@ function system_theme_default() {
     else {
       drupal_set_message(t('The %theme theme was not found.', array('%theme' => $theme)), 'error');
     }
-    drupal_goto('admin/appearance');
+    return new RedirectResponse('admin/appearance');
   }
   throw new AccessDeniedHttpException();
 }
@@ -1376,7 +1377,7 @@ function system_modules_uninstall_validate($form, &$form_state) {
   // Form submitted, but no modules selected.
   if (!count(array_filter($form_state['values']['uninstall']))) {
     drupal_set_message(t('No modules selected.'), 'error');
-    drupal_goto('admin/modules/uninstall');
+    return new RedirectResponse('admin/modules/uninstall');
   }
 }
 
@@ -1590,7 +1591,7 @@ function system_run_cron_submit($form, &$form_state) {
     drupal_set_message(t('Cron run failed.'), 'error');
   }
 
-  drupal_goto('admin/config/system/cron');
+  return new RedirectResponse('admin/config/system/cron');
 }
 
 /**
@@ -2127,7 +2128,7 @@ function system_run_cron() {
     drupal_set_message(t('Cron run failed.'), 'error');
   }
 
-  drupal_goto('admin/reports/status');
+  return new RedirectResponse('admin/reports/status');
 }
 
 /**
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 13aa535..14b9479 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -3020,17 +3020,17 @@ function hook_install_tasks(&$install_state) {
 }
 
 /**
- * Change the page the user is sent to by drupal_goto().
+ * Change the page the user is sent to within a RedirectResponse.
  *
  * @param $path
  *   A Drupal path or a full URL.
  * @param $options
  *   An associative array of additional URL options to pass to url().
  * @param $http_response_code
- *   The HTTP status code to use for the redirection. See drupal_goto() for more
- *   information.
+ *   The HTTP status code to use for the redirection. See RedirectResponse for
+ *   more information.
  */
-function hook_drupal_goto_alter(&$path, &$options, &$http_response_code) {
+function hook_redirect_response_alter(&$path, &$options, &$http_response_code) {
   // A good addition to misery module.
   $http_response_code = 500;
 }
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index ebe5548..7cb7094 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -15,6 +15,7 @@
 use Symfony\Component\HttpFoundation\Response;
 use Guzzle\Http\Exception\BadResponseException;
 use Guzzle\Http\Exception\RequestException;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 
 /**
  * Maximum age of temporary files in seconds.
@@ -2366,8 +2367,8 @@ function _system_themes_access($theme) {
  * or to call system_authorized_init() and then redirect to authorize.php,
  * using the URL from system_authorized_get_url(). Redirecting yourself is
  * necessary when your authorized operation is being triggered by a form
- * submit handler, since calling drupal_goto() in a submit handler is a bad
- * idea, and you should instead set $form_state['redirect'].
+ * submit handler, since calling new RedirectResponse in a submit handler is a
+ * bad idea, and you should instead set $form_state['redirect'].
  *
  * Once the SESSION is setup for the operation and the user is redirected to
  * authorize.php, they will be prompted for their connection credentials (core
@@ -2394,7 +2395,7 @@ function _system_themes_access($theme) {
  * not to assume any code exists. Example (system_authorized_run()):
  * @code
  *   system_authorized_init($callback, $file, $arguments, $page_title);
- *   drupal_goto(system_authorized_get_url());
+ *   return new RedirectResponse(system_authorized_get_url());
  * @endcode
  * Example (update_manager_install_form_submit()):
  * @code
@@ -2467,7 +2468,7 @@ function system_authorized_batch_processing_url() {
  */
 function system_authorized_run($callback, $file, $arguments = array(), $page_title = NULL) {
   system_authorized_init($callback, $file, $arguments, $page_title);
-  drupal_goto(system_authorized_get_url());
+  return new RedirectResponse(system_authorized_get_url());
 }
 
 /**
@@ -3473,7 +3474,7 @@ function system_admin_compact_mode() {
  */
 function system_admin_compact_page($mode = 'off') {
   user_cookie_save(array('admin_compact_mode' => ($mode == 'on')));
-  drupal_goto();
+  return new RedirectResponse('<front>');
 }
 
 /**
diff --git a/core/modules/system/tests/modules/common_test/common_test.module b/core/modules/system/tests/modules/common_test/common_test.module
index 203ead2..2e48406 100644
--- a/core/modules/system/tests/modules/common_test/common_test.module
+++ b/core/modules/system/tests/modules/common_test/common_test.module
@@ -5,44 +5,47 @@
  * Helper module for the Common tests.
  */
 
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
 /**
  * Implements hook_menu().
  */
 function common_test_menu() {
-  $items['common-test/drupal_goto'] = array(
+  $items['common-test/redirect-response'] = array(
     'title' => 'Drupal Goto',
-    'page callback' => 'common_test_drupal_goto_land',
+    'page callback' => 'common_test_redirect_response_land',
     'access arguments' => array('access content'),
     'type' => MENU_CALLBACK,
   );
-  $items['common-test/drupal_goto/alternative'] = array(
+  $items['common-test/redirect-response/alternative'] = array(
     'title' => 'Drupal Goto',
-    'page callback' => 'common_test_drupal_goto_land_alternative',
+    'page callback' => 'common_test_redirect_response_land_alternative',
     'access arguments' => array('access content'),
     'type' => MENU_CALLBACK,
   );
-  $items['common-test/drupal_goto/fail'] = array(
+  $items['common-test/redirect-response/fail'] = array(
     'title' => 'Drupal Goto',
-    'page callback' => 'common_test_drupal_goto_land_fail',
+    'page callback' => 'common_test_redirect_response_land_fail',
     'access arguments' => array('access content'),
     'type' => MENU_CALLBACK,
   );
-  $items['common-test/drupal_goto/redirect'] = array(
+  $items['common-test/redirect-response/redirect'] = array(
     'title' => 'Drupal Goto',
-    'page callback' => 'common_test_drupal_goto_redirect',
+    'page callback' => 'common_test_redirect_response_redirect',
     'access arguments' => array('access content'),
     'type' => MENU_CALLBACK,
   );
-  $items['common-test/drupal_goto/redirect_advanced'] = array(
+  $items['common-test/redirect-response/redirect_advanced'] = array(
     'title' => 'Drupal Goto',
-    'page callback' => 'common_test_drupal_goto_redirect_advanced',
+    'page callback' => 'common_test_redirect_response_redirect_advanced',
     'access arguments' => array('access content'),
     'type' => MENU_CALLBACK,
   );
-  $items['common-test/drupal_goto/redirect_fail'] = array(
+  // @todo build a callback function for use here.
+  $items['common-test/redirect-response/redirect_fail'] = array(
     'title' => 'Drupal Goto Failure',
-    'page callback' => 'drupal_goto',
-    'page arguments' => array('common-test/drupal_goto/fail'),
+    'page callback' => 'common_test_redirect_response_redirect',
+    'page arguments' => array('common-test/redirect-response/fail'),
     'access arguments' => array('access content'),
     'type' => MENU_CALLBACK,
   );
@@ -74,52 +77,52 @@ function common_test_menu() {
 }
 
 /**
- * Redirects using drupal_goto().
+ * Redirects using RedirectResponse.
  */
-function common_test_drupal_goto_redirect() {
-  drupal_goto('common-test/drupal_goto');
+function common_test_redirect_response_redirect() {
+  return new RedirectResponse('common-test/redirect-response');
 }
 
 /**
- * Redirects using drupal_goto().
+ * Redirects using RedirectResponse.
  */
-function common_test_drupal_goto_redirect_advanced() {
-  drupal_goto('common-test/drupal_goto', array('query' => array('foo' => '123')), 301);
+function common_test_redirect_response_redirect_advanced() {
+  return new RedirectResponse('common-test/redirect-response', 301, array('query' => array('foo' => '123')));
 }
 
 /**
- * Page callback: Provides a landing page for drupal_goto().
+ * Page callback: Provides a landing page for RedirectResponse.
  *
  * @see common_test_menu()
  */
-function common_test_drupal_goto_land() {
-  print "drupal_goto";
+function common_test_redirect_response_land() {
+  print "RedirectResponse";
 }
 
 /**
- * Page callback: Provides a landing page for drupal_goto().
+ * Page callback: Provides a landing page for RedirectResponse.
  *
  * @see common_test_menu()
  */
-function common_test_drupal_goto_land_alternative() {
-  print "drupal_goto_alternative";
+function common_test_redirect_response_land_alternative() {
+  print "RedirectResponse_alternative";
 }
 
 /**
- * Page callback: Provides a failure landing page for drupal_goto().
+ * Page callback: Provides a failure landing page for RedirectResponse.
  *
  * @see common_test_menu()
  */
-function common_test_drupal_goto_land_fail() {
-  print "drupal_goto_fail";
+function common_test_redirect_response_land_fail() {
+  print "RedirectResponse_fail";
 }
 
 /**
- * Implements hook_drupal_goto_alter().
+ * Implements hook_redirect_response_alter().
  */
-function common_test_drupal_goto_alter(&$path, &$options, &$http_response_code) {
-  if ($path == 'common-test/drupal_goto/fail') {
-    $path = 'common-test/drupal_goto/redirect';
+function common_test_redirect_response_alter(&$path, &$options, &$http_response_code) {
+  if ($path == 'common-test/redirect-response/fail') {
+    $path = 'common-test/redirect-response/redirect';
   }
 }
 
diff --git a/core/modules/system/tests/modules/session_test/session_test.module b/core/modules/system/tests/modules/session_test/session_test.module
index b3e82fd..c90028f 100644
--- a/core/modules/system/tests/modules/session_test/session_test.module
+++ b/core/modules/system/tests/modules/session_test/session_test.module
@@ -162,12 +162,12 @@ function session_test_form_user_login_form_alter(&$form) {
 }
 
 /**
- * Implements hook_drupal_goto_alter().
+ * Implements hook_redirect_response_alter().
  *
  * Force the redirection to go to a non-secure page after being on a secure
  * page through https.php.
  */
-function session_test_drupal_goto_alter(&$path, &$options, &$http_response_code) {
+function session_test_redirect_response_alter(&$path, &$options, &$http_response_code) {
   global $base_insecure_url, $is_https_mock;
   // Alter the redirect to use HTTP when using a mock HTTPS request through
   // https.php because form submissions would otherwise redirect to a
diff --git a/core/modules/system/tests/modules/system_test/system_test.module b/core/modules/system/tests/modules/system_test/system_test.module
index 4494299..4cda5b3 100644
--- a/core/modules/system/tests/modules/system_test/system_test.module
+++ b/core/modules/system/tests/modules/system_test/system_test.module
@@ -378,5 +378,5 @@ function system_test_filetransfer_info() {
 function system_test_authorize_init_page($page_title) {
   $authorize_url = $GLOBALS['base_url'] . '/core/authorize.php';
   system_authorized_init('system_test_authorize_run', drupal_get_path('module', 'system_test') . '/system_test.module', array(), $page_title);
-  drupal_goto($authorize_url);
+  return new RedirectResponse($authorize_url);
 }
diff --git a/core/modules/update/update.manager.inc b/core/modules/update/update.manager.inc
index 0047a52..b474961 100644
--- a/core/modules/update/update.manager.inc
+++ b/core/modules/update/update.manager.inc
@@ -38,6 +38,7 @@
 
 use Drupal\Core\Updater\Updater;
 use Drupal\Core\FileTransfer\Local;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 
 /**
  * @defgroup update_manager_update Update Manager module: update
@@ -356,7 +357,7 @@ function update_manager_download_batch_finished($success, $results) {
   elseif ($success) {
     drupal_set_message(t('Updates downloaded successfully.'));
     $_SESSION['update_manager_update_projects'] = $results['projects'];
-    drupal_goto('admin/update/ready');
+    return new RedirectResponse('admin/update/ready');
   }
   else {
     // Ideally we're catching all Exceptions, so they should never see this,
diff --git a/core/modules/user/lib/Drupal/user/RegisterFormController.php b/core/modules/user/lib/Drupal/user/RegisterFormController.php
index f84a3df..35279e5 100644
--- a/core/modules/user/lib/Drupal/user/RegisterFormController.php
+++ b/core/modules/user/lib/Drupal/user/RegisterFormController.php
@@ -8,6 +8,7 @@
 namespace Drupal\user;
 
 use Drupal\Core\Entity\EntityInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 
 /**
  * Form controller for the user register forms.
@@ -32,7 +33,7 @@ public function form(array $form, array &$form_state, EntityInterface $account)
 
     // If we aren't admin but already logged on, go to the user page instead.
     if (!$admin && $user->uid) {
-      drupal_goto('user/' . $user->uid);
+      return new RedirectResponse('user/' . $user->uid);
     }
 
     $form['#attached']['library'][] = array('system', 'jquery.cookie');
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index d41206f..74f5a09 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -1060,7 +1060,7 @@ function user_menu() {
 /**
  * Implements hook_menu_site_status_alter().
  */
-function user_menu_site_status_alter(&$menu_site_status, $path) {
+function user_menu_site_status_alter(&$menu_site_status, $path, &$redirect_path) {
   if ($menu_site_status == MENU_SITE_OFFLINE) {
     // If the site is offline, log out unprivileged users.
     if (user_is_logged_in() && !user_access('access site in maintenance mode')) {
@@ -1072,7 +1072,8 @@ function user_menu_site_status_alter(&$menu_site_status, $path) {
       switch ($path) {
         case 'user':
           // Forward anonymous user to login page.
-          drupal_goto('user/login');
+          $redirect_path = 'user/login';
+          break;
         case 'user/login':
         case 'user/password':
           // Disable offline mode.
@@ -1090,11 +1091,11 @@ function user_menu_site_status_alter(&$menu_site_status, $path) {
   if (user_is_logged_in()) {
     if ($path == 'user/login') {
       // If user is logged in, redirect to 'user' instead of giving 403.
-      drupal_goto('user');
+      $redirect_path = 'user';
     }
     if ($path == 'user/register') {
       // Authenticated user should be redirected to user edit page.
-      drupal_goto('user/' . $GLOBALS['user']->uid . '/edit');
+      $redirect_path = 'user/' . $GLOBALS['user']->uid . '/edit';
     }
   }
 }
@@ -2146,7 +2147,7 @@ function user_multiple_cancel_confirm($form, &$form_state) {
     drupal_set_message($message, $redirect ? 'error' : 'warning');
     // If only user 1 was selected, redirect to the overview.
     if ($redirect) {
-      drupal_goto('admin/people');
+      return new RedirectResponse('admin/people');
     }
   }
 
diff --git a/core/modules/user/user.pages.inc b/core/modules/user/user.pages.inc
index 140b767..3255f06 100644
--- a/core/modules/user/user.pages.inc
+++ b/core/modules/user/user.pages.inc
@@ -101,7 +101,7 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a
         drupal_set_message(t('The one-time login link you clicked is invalid.'));
       }
     }
-    drupal_goto();
+    return new RedirectResponse('<front>');
   }
   else {
     // Time out, in seconds, until login URL expires.
@@ -113,7 +113,7 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a
       // No time out for first time login.
       if ($account->login && $current - $timestamp > $timeout) {
         drupal_set_message(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'));
-        drupal_goto('user/password');
+        return new RedirectResponse('user/password');
       }
       elseif ($account->uid && $timestamp >= $account->login && $timestamp <= $current && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login)) {
         // First stage is a confirmation form, then login
@@ -128,7 +128,7 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a
           // Let the user's password be changed without the current password check.
           $token = drupal_hash_base64(drupal_random_bytes(55));
           $_SESSION['pass_reset_' . $user->uid] = $token;
-          drupal_goto('user/' . $user->uid . '/edit', array('query' => array('pass-reset-token' => $token)));
+          return new RedirectResponse('user/' . $user->uid . '/edit', array('query' => array('pass-reset-token' => $token)));
         }
         else {
           if (!$account->login) {
@@ -147,7 +147,7 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a
       }
       else {
         drupal_set_message(t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'));
-        drupal_goto('user/password');
+        return new RedirectResponse('user/password');
       }
     }
     else {
@@ -171,7 +171,7 @@ function user_logout() {
   // Destroy the current session, and reset $user to the anonymous user.
   session_destroy();
 
-  drupal_goto();
+  return new RedirectResponse('<front>');
 }
 
 /**
@@ -394,7 +394,7 @@ function user_cancel_confirm($account, $timestamp = 0, $hashed_pass = '') {
     }
     else {
       drupal_set_message(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'));
-      drupal_goto("user/$account->uid/cancel");
+      return new RedirectResponse("user/$account->uid/cancel");
     }
   }
   throw new AccessDeniedHttpException();
@@ -409,7 +409,7 @@ function user_cancel_confirm($account, $timestamp = 0, $hashed_pass = '') {
 function user_page() {
   global $user;
   if ($user->uid) {
-    return new RedirectResponse(url('user/' . $user->uid, array('absolute' => TRUE)));
+    return new RedirectResponse('user/' . $user->uid);
   }
   else {
     return drupal_get_form('user_login_form');
diff --git a/core/modules/views/views_ui/lib/Drupal/views_ui/ViewEditFormController.php b/core/modules/views/views_ui/lib/Drupal/views_ui/ViewEditFormController.php
index 0d06d9e..acf45ed 100644
--- a/core/modules/views/views_ui/lib/Drupal/views_ui/ViewEditFormController.php
+++ b/core/modules/views/views_ui/lib/Drupal/views_ui/ViewEditFormController.php
@@ -259,7 +259,7 @@ public function submit(array $form, array &$form_state) {
         if (($display->getPluginId() == 'page') && ($old_path == $destination) && ($old_path != $view->get('executable')->displayHandlers->get($id)->getOption('path'))) {
           $destination = $view->get('executable')->displayHandlers->get($id)->getOption('path');
           $query->remove('destination');
-          // @todo For whatever reason drupal_goto is still using $_GET.
+          // @todo For whatever reason RedirectResponse uses query parameters.
           // @see http://drupal.org/node/1668866
           unset($_GET['destination']);
         }
diff --git a/core/modules/views/views_ui/views_ui.module b/core/modules/views/views_ui/views_ui.module
index 6bae565..870c9ca 100644
--- a/core/modules/views/views_ui/views_ui.module
+++ b/core/modules/views/views_ui/views_ui.module
@@ -11,6 +11,7 @@
 use Drupal\views\Analyzer;
 use Drupal\Core\Ajax\AjaxResponse;
 use Drupal\Core\Ajax\ReplaceCommand;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 
 /**
  * Implements hook_menu().
