diff --git a/core/core.services.yml b/core/core.services.yml
index a8313a6df4f33c0307c371295906451622a29aa4..e1613195685a6d0d858f4dcfe620d76202261283 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -19,7 +19,7 @@ services:
       - { name: cache.context}
   cache_context.theme:
     class: Drupal\Core\Cache\ThemeCacheContext
-    arguments: ['@request_stack', '@theme.negotiator']
+    arguments: ['@current_route_match', '@theme.negotiator']
     tags:
       - { name: cache.context}
   cache_context.timezone:
@@ -201,7 +201,7 @@ services:
       - { name: service_collector, tag: http_client_subscriber, call: attach }
   theme.negotiator:
     class: Drupal\Core\Theme\ThemeNegotiator
-    arguments: ['@access_check.theme', '@request_stack']
+    arguments: ['@access_check.theme']
     tags:
       - { name: service_collector, tag: theme_negotiator, call: addNegotiator }
   theme.negotiator.default:
@@ -211,7 +211,7 @@ services:
       - { name: theme_negotiator, priority: -100 }
   theme.negotiator.ajax_base_page:
     class: Drupal\Core\Theme\AjaxBasePageNegotiator
-    arguments: ['@csrf_token', '@config.factory']
+    arguments: ['@csrf_token', '@config.factory', '@request_stack']
     tags:
       - { name: theme_negotiator, priority: 1000 }
   container.namespaces:
@@ -273,6 +273,9 @@ services:
     class: Symfony\Component\HttpFoundation\RequestStack
     tags:
       - { name: persist }
+  current_route_match:
+     class: Drupal\Core\Routing\CurrentRouteMatch
+     arguments: ['@request_stack']
   event_dispatcher:
     class: Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher
     arguments: ['@service_container']
diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 1de74bf411251ee5130d59b979db91acd6867494..3b4b5c1fe26f49e268e61a5baa3bb54e5075d64e 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -8,7 +8,6 @@
 use Drupal\Component\Utility\String;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Template\Attribute;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 
 /**
  * @defgroup menu Menu and routing system
@@ -479,7 +478,7 @@ function menu_local_tasks($level = 0) {
     $data['tabs'] = array();
     $data['actions'] = array();
 
-    $route_name = \Drupal::request()->attributes->get(RouteObjectInterface::ROUTE_NAME);
+    $route_name = \Drupal::routeMatch()->getRouteName();
     if (!empty($route_name)) {
       $manager = \Drupal::service('plugin.manager.menu.local_task');
       $local_tasks = $manager->getTasksBuild($route_name);
@@ -534,7 +533,7 @@ function menu_secondary_local_tasks() {
  */
 function menu_get_local_actions() {
   $links = menu_local_tasks();
-  $route_name = Drupal::request()->attributes->get(RouteObjectInterface::ROUTE_NAME);
+  $route_name = Drupal::routeMatch()->getRouteName();
   $manager = \Drupal::service('plugin.manager.menu.local_action');
   return $manager->getActionsForRoute($route_name) + $links['actions'];
 }
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 03d0df0c2cb5f0d9d4f094774ac600537d39332c..18218af7d28880907d7b1e32978860c19a63083f 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -104,8 +104,7 @@ function drupal_theme_initialize() {
   // @todo Let the theme.negotiator listen to the kernel request event.
   // Determine the active theme for the theme negotiator service. This includes
   // the default theme as well as really specific ones like the ajax base theme.
-  $request = \Drupal::request();
-  $theme = \Drupal::service('theme.negotiator')->determineActiveTheme($request);
+  $theme = \Drupal::service('theme.negotiator')->determineActiveTheme(\Drupal::routeMatch());
 
   // If no theme could be negotiated, or if the negotiated theme is not within
   // the list of enabled themes, fall back to the default theme output of core
@@ -2133,7 +2132,7 @@ function template_preprocess_page(&$variables) {
     );
   }
 
-  if ($node = \Drupal::request()->attributes->get('node')) {
+  if ($node = \Drupal::routeMatch()->getParameter('node')) {
     $variables['node'] = $node;
   }
 
diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php
index 57cf2ef9f4222f72f1acd4cb2c63ac8057f0c49b..6409fd38c0b99c2c7cf3d47dca6fb3028ce9ba26 100644
--- a/core/lib/Drupal.php
+++ b/core/lib/Drupal.php
@@ -187,6 +187,16 @@ public static function request() {
     return static::$container->get('request');
   }
 
+  /**
+   * Retrieves the currently active route match object.
+   *
+   * @return \Drupal\Core\Routing\RouteMatchInterface
+   *   The currently active route match object.
+   */
+  public static function routeMatch() {
+    return static::$container->get('current_route_match');
+  }
+
   /**
    * Gets the current active user.
    *
diff --git a/core/lib/Drupal/Core/Cache/ThemeCacheContext.php b/core/lib/Drupal/Core/Cache/ThemeCacheContext.php
index a11671b8feb37627623191f8490989a44f052dd4..5d0bd3e6e809a07b01f090a504263d3ed61ccae6 100644
--- a/core/lib/Drupal/Core/Cache/ThemeCacheContext.php
+++ b/core/lib/Drupal/Core/Cache/ThemeCacheContext.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Cache;
 
-use Symfony\Component\HttpFoundation\RequestStack;
+use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Theme\ThemeNegotiatorInterface;
 
 /**
@@ -16,11 +16,11 @@
 class ThemeCacheContext implements CacheContextInterface {
 
   /**
-   * The request stack.
+   * The current route match.
    *
-   * @var \Symfony\Component\HttpFoundation\RequestStack
+   * @var \Drupal\Core\Routing\RouteMatch
    */
-  protected $requestStack;
+  protected $routeMatch;
 
   /**
    * The theme negotiator.
@@ -32,13 +32,13 @@ class ThemeCacheContext implements CacheContextInterface {
   /**
    * Constructs a new ThemeCacheContext service.
    *
-   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
-   *   The request stack.
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The route match.
    * @param \Drupal\Core\Theme\ThemeNegotiatorInterface $theme_negotiator
    *   The theme negotiator.
    */
-  public function __construct(RequestStack $request_stack, ThemeNegotiatorInterface $theme_negotiator) {
-    $this->requestStack = $request_stack;
+  public function __construct(RouteMatchInterface $route_match, ThemeNegotiatorInterface $theme_negotiator) {
+    $this->routeMatch = $route_match;
     $this->themeNegotiator = $theme_negotiator;
   }
 
@@ -53,8 +53,7 @@ public static function getLabel() {
    * {@inheritdoc}
    */
   public function getContext() {
-    $request = $this->requestStack->getCurrentRequest();
-    return $this->themeNegotiator->determineActiveTheme($request) ?: 'stark';
+    return $this->themeNegotiator->determineActiveTheme($this->routeMatch) ?: 'stark';
   }
 
 }
diff --git a/core/lib/Drupal/Core/Routing/CurrentRouteMatch.php b/core/lib/Drupal/Core/Routing/CurrentRouteMatch.php
new file mode 100644
index 0000000000000000000000000000000000000000..b18893057e2e1da84c07128687680b17eb2ffabe
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/CurrentRouteMatch.php
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Routing\CurrentRouteMatch.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Default object for current_route_match service.
+ */
+class CurrentRouteMatch implements RouteMatchInterface {
+
+  /**
+   * The related request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * Internal cache of RouteMatch objects.
+   *
+   * @var \SplObjectStorage
+   */
+  protected $routeMatches;
+
+  /**
+   * Constructs a CurrentRouteMatch object.
+   *
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *  The request stack.
+   */
+  public function __construct(RequestStack $request_stack) {
+    $this->requestStack = $request_stack;
+    $this->routeMatches = new \SplObjectStorage();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouteName() {
+    return $this->getCurrentRouteMatch()->getRouteName();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouteObject() {
+    return $this->getCurrentRouteMatch()->getRouteObject();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getParameter($parameter_name) {
+    return $this->getCurrentRouteMatch()->getParameter($parameter_name);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getParameters() {
+    return $this->getCurrentRouteMatch()->getParameters();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRawParameter($parameter_name) {
+    return $this->getCurrentRouteMatch()->getRawParameter($parameter_name);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRawParameters() {
+    return $this->getCurrentRouteMatch()->getRawParameters();
+  }
+
+  /**
+   * Returns the route match for the current request.
+   *
+   * @return \Drupal\Core\Routing\RouteMatchInterface
+   *   The current route match object.
+   */
+  protected function getCurrentRouteMatch() {
+    return $this->getRouteMatch($this->requestStack->getCurrentRequest());
+  }
+
+  /**
+   * Returns the route match for a passed in request.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   A request object.
+   *
+   * @return \Drupal\Core\Routing\RouteMatchInterface
+   *   A route match object created from the request.
+   */
+  protected function getRouteMatch(Request $request) {
+    if (isset($this->routeMatches[$request])) {
+      $route_match = $this->routeMatches[$request];
+    }
+    else {
+      $route_match = RouteMatch::createFromRequest($request);
+
+      // Since getRouteMatch() might be invoked both before and after routing
+      // is completed, only statically cache the route match after there's a
+      // matched route.
+      if ($route_match->getRouteObject()) {
+        $this->routeMatches[$request] = $route_match;
+      }
+    }
+    return $route_match;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Routing/NullRouteMatch.php b/core/lib/Drupal/Core/Routing/NullRouteMatch.php
new file mode 100644
index 0000000000000000000000000000000000000000..0ef2c3b300dfa722d0960bcdd16f54e33abb547f
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/NullRouteMatch.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Routing\NullRouteMatch.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Component\HttpFoundation\ParameterBag;
+
+/**
+ * Stub implementation of RouteMatchInterface for when there's no matched route.
+ */
+class NullRouteMatch implements RouteMatchInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouteName() {
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouteObject() {
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getParameter($parameter_name) {
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getParameters() {
+    return new ParameterBag();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRawParameter($parameter_name) {
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRawParameters() {
+    return new ParameterBag();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Routing/RouteMatch.php b/core/lib/Drupal/Core/Routing/RouteMatch.php
new file mode 100644
index 0000000000000000000000000000000000000000..eafb1842959f2734737d5acbe655ece5bf023e82
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/RouteMatch.php
@@ -0,0 +1,165 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Routing\RouteMatch.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Symfony\Component\HttpFoundation\ParameterBag;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Default object representing the results of routing.
+ */
+class RouteMatch implements RouteMatchInterface {
+
+  /**
+   * The route name.
+   *
+   * @var string
+   */
+  protected $routeName;
+
+  /**
+   * The route.
+   *
+   * @var \Symfony\Component\Routing\Route
+   */
+  protected $route;
+
+  /**
+   * A key|value store of parameters.
+   *
+   * @var \Symfony\Component\HttpFoundation\ParameterBag
+   */
+  protected $parameters;
+
+  /**
+   * A key|value store of raw parameters.
+   *
+   * @var \Symfony\Component\HttpFoundation\ParameterBag
+   */
+  protected $rawParameters;
+
+  /**
+   * Constructs a RouteMatch object.
+   *
+   * @param string $route_name
+   *  The name of the route.
+   * @param \Symfony\Component\Routing\Route $route
+   *   The route.
+   * @param array $parameters
+   *   The parameters array.
+   * @param array $raw_parameters
+   *   The raw $parameters array.
+   */
+  public function __construct($route_name, Route $route, array $parameters = array(), array $raw_parameters = array()) {
+    $this->routeName = $route_name;
+    $this->route = $route;
+
+    // Pre-filter parameters.
+    $route_params = $this->getParameterNames();
+    $parameters = array_intersect_key($parameters, $route_params);
+    $raw_parameters = array_intersect_key($raw_parameters, $route_params);
+    $this->parameters = new ParameterBag($parameters);
+    $this->rawParameters = new ParameterBag($raw_parameters);
+  }
+
+  /**
+   * Creates a RouteMatch from a request.
+   *
+   * @param Request $request
+   *   A request object.
+   *
+   * @return \Drupal\Core\Routing\RouteMatchInterface
+   *   A new RouteMatch object if there's a matched route for the request.
+   *   A new NullRouteMatch object otherwise (e.g., on a 404 page or when
+   *   invoked prior to routing).
+   */
+  public static function createFromRequest(Request $request) {
+    if ($request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)) {
+      $raw_variables = array();
+      if ($raw = $request->attributes->get('_raw_variables')) {
+        $raw_variables = $raw->all();
+      }
+      return new static(
+        $request->attributes->get(RouteObjectInterface::ROUTE_NAME),
+        $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT),
+        $request->attributes->all(),
+        $raw_variables);
+    }
+    else {
+      return new NullRouteMatch();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouteName() {
+    return $this->routeName;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouteObject() {
+    return $this->route;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getParameter($parameter_name) {
+    return $this->parameters->get($parameter_name);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getParameters() {
+    return $this->parameters;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRawParameter($parameter_name) {
+    return $this->rawParameters->get($parameter_name);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRawParameters() {
+    return $this->rawParameters;
+  }
+
+  /**
+   * Returns the names of all parameters for the currently matched route.
+   *
+   * @return array
+   *   Route parameter names as both the keys and values.
+   */
+  protected function getParameterNames() {
+    $names = array();
+    if ($route = $this->getRouteObject()) {
+      // Variables defined in path and host patterns are route parameters.
+      $variables = $route->compile()->getVariables();
+      $names = array_combine($variables, $variables);
+      // Route defaults that do not start with a leading "_" are also
+      // parameters, even if they are not included in path or host patterns.
+      foreach ($route->getDefaults() as $name => $value) {
+        if (!isset($names[$name]) && substr($name, 0, 1) !== '_') {
+          $names[$name] = $name;
+        }
+      }
+    }
+    return $names;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Routing/RouteMatchInterface.php b/core/lib/Drupal/Core/Routing/RouteMatchInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..f0d9aa8dee4c57a78483f1ae6861d34b1948696d
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/RouteMatchInterface.php
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Routing\RouteMatchInterface.
+ */
+
+namespace Drupal\Core\Routing;
+
+/**
+ * Provides an interface for classes representing the result of routing.
+ *
+ * Routing is the process of selecting the best matching candidate from a
+ * collection of routes for an incoming request. The relevant properties of a
+ * request include the path as well as a list of raw parameter values derived
+ * from the URL. If an appropriate route is found, raw parameter values will be
+ * upcast automatically if possible.
+ *
+ * The route match object contains useful information about the selected route
+ * as well as the raw and upcast parameters derived from the incoming
+ * request.
+ */
+interface RouteMatchInterface {
+
+  /**
+   * Returns the route name.
+   *
+   * @return string|null
+   *   The route name. NULL if no route is matched.
+   */
+  public function getRouteName();
+
+  /**
+   * Returns the route object.
+   *
+   * @return \Symfony\Component\Routing\Route|null
+   *   The route object. NULL if no route is matched.
+   */
+  public function getRouteObject();
+
+  /**
+   * Returns the value of a named route parameter.
+   *
+   * @param string $parameter_name
+   *   The parameter name.
+   *
+   * @return mixed|null
+   *   The parameter value. NULL if the route doesn't define the parameter or
+   *   if the parameter value can't be determined from the request.
+   */
+  public function getParameter($parameter_name);
+
+  /**
+   * Returns the bag of all route parameters.
+   *
+   * @return \Symfony\Component\HttpFoundation\ParameterBag
+   *   The parameter bag.
+   */
+  public function getParameters();
+
+  /**
+   * Returns the raw value of a named route parameter.
+   *
+   * @param string $parameter_name
+   *   The parameter name.
+   *
+   * @return string|null
+   *   The raw (non-upcast) parameter value. NULL if the route doesn't define
+   *   the parameter or if the raw parameter value can't be determined from the
+   *   request.
+   */
+  public function getRawParameter($parameter_name);
+
+  /**
+   * Returns the bag of all raw route parameters.
+   *
+   * @return \Symfony\Component\HttpFoundation\ParameterBag
+   *   The parameter bag.
+   */
+  public function getRawParameters();
+
+}
diff --git a/core/lib/Drupal/Core/Theme/AjaxBasePageNegotiator.php b/core/lib/Drupal/Core/Theme/AjaxBasePageNegotiator.php
index d3a4bac4caf7db605952ea76448c40442b1bbfda..b15ce93bef7b140e636443efdd5f7c65e4fbef88 100644
--- a/core/lib/Drupal/Core/Theme/AjaxBasePageNegotiator.php
+++ b/core/lib/Drupal/Core/Theme/AjaxBasePageNegotiator.php
@@ -9,8 +9,8 @@
 
 use Drupal\Core\Access\CsrfTokenGenerator;
 use Drupal\Core\Config\ConfigFactoryInterface;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
-use Symfony\Component\HttpFoundation\Request;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
 
 /**
  * Defines a theme negotiator that deals with the active theme on ajax requests.
@@ -45,6 +45,13 @@ class AjaxBasePageNegotiator implements ThemeNegotiatorInterface {
    */
   protected $configFactory;
 
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
   /**
    * Constructs a new AjaxBasePageNegotiator.
    *
@@ -52,18 +59,21 @@ class AjaxBasePageNegotiator implements ThemeNegotiatorInterface {
    *   The CSRF token generator.
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The config factory.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack used to retrieve the current request.
    */
-  public function __construct(CsrfTokenGenerator $token_generator, ConfigFactoryInterface $config_factory) {
+  public function __construct(CsrfTokenGenerator $token_generator, ConfigFactoryInterface $config_factory, RequestStack $request_stack) {
     $this->csrfGenerator = $token_generator;
     $this->configFactory = $config_factory;
+    $this->requestStack = $request_stack;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function applies(Request $request) {
+  public function applies(RouteMatchInterface $route_match) {
     // Check whether the route was configured to use the base page theme.
-    return ($route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT))
+    return ($route = $route_match->getRouteObject())
       && $route->hasOption('_theme')
       && $route->getOption('_theme') == 'ajax_base_page';
   }
@@ -71,8 +81,8 @@ public function applies(Request $request) {
   /**
    * {@inheritdoc}
    */
-  public function determineActiveTheme(Request $request) {
-    if (($ajax_page_state = $request->request->get('ajax_page_state'))  && !empty($ajax_page_state['theme']) && !empty($ajax_page_state['theme_token'])) {
+  public function determineActiveTheme(RouteMatchInterface $route_match) {
+    if (($ajax_page_state = $this->requestStack->getCurrentRequest()->request->get('ajax_page_state'))  && !empty($ajax_page_state['theme']) && !empty($ajax_page_state['theme_token'])) {
       $theme = $ajax_page_state['theme'];
       $token = $ajax_page_state['theme_token'];
 
diff --git a/core/lib/Drupal/Core/Theme/DefaultNegotiator.php b/core/lib/Drupal/Core/Theme/DefaultNegotiator.php
index 22b5863b364df478c1785caeb8d1f7b2322ee724..2f0e6f78138b0a37a6ccc9bd5858d86eb7e7d8e1 100644
--- a/core/lib/Drupal/Core/Theme/DefaultNegotiator.php
+++ b/core/lib/Drupal/Core/Theme/DefaultNegotiator.php
@@ -8,7 +8,7 @@
 namespace Drupal\Core\Theme;
 
 use Drupal\Core\Config\ConfigFactoryInterface;
-use Symfony\Component\HttpFoundation\Request;
+use Drupal\Core\Routing\RouteMatchInterface;
 
 /**
  * Determines the default theme of the site.
@@ -35,14 +35,14 @@ public function __construct(ConfigFactoryInterface $config_factory) {
   /**
    * {@inheritdoc}
    */
-  public function applies(Request $request) {
+  public function applies(RouteMatchInterface $route_match) {
     return TRUE;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function determineActiveTheme(Request $request) {
+  public function determineActiveTheme(RouteMatchInterface $route_match) {
     return $this->config->get('default');
   }
 
diff --git a/core/lib/Drupal/Core/Theme/ThemeNegotiator.php b/core/lib/Drupal/Core/Theme/ThemeNegotiator.php
index 054d6be8ffc364598d4a624b9e950c57018f2f77..8c1ef0330847065051021cbe6d83d86a0dfa1eca 100644
--- a/core/lib/Drupal/Core/Theme/ThemeNegotiator.php
+++ b/core/lib/Drupal/Core/Theme/ThemeNegotiator.php
@@ -7,8 +7,7 @@
 
 namespace Drupal\Core\Theme;
 
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\RequestStack;
+use Drupal\Core\Routing\RouteMatchInterface;
 
 /**
  * Provides a class which determines the active theme of the page.
@@ -37,13 +36,6 @@ class ThemeNegotiator implements ThemeNegotiatorInterface {
    */
   protected $sortedNegotiators;
 
-  /**
-   * The request stack.
-   *
-   * @var \Symfony\Component\HttpFoundation\RequestStack
-   */
-  protected $requestStack;
-
   /**
    * The access checker for themes.
    *
@@ -57,9 +49,8 @@ class ThemeNegotiator implements ThemeNegotiatorInterface {
    * @param \Drupal\Core\Theme\ThemeAccessCheck $theme_access
    *   The access checker for themes.
    */
-  public function __construct(ThemeAccessCheck $theme_access, RequestStack $request_stack) {
+  public function __construct(ThemeAccessCheck $theme_access) {
     $this->themeAccess = $theme_access;
-    $this->requestStack = $request_stack;
   }
 
   /**
@@ -96,37 +87,22 @@ protected function getSortedNegotiators() {
     return $this->sortedNegotiators;
   }
 
-  /**
-   * Get the current active theme.
-   *
-   * @return string
-   *   The current active string.
-   */
-  public function getActiveTheme() {
-    $request = $this->requestStack->getCurrentRequest();
-    if (!$request->attributes->has('_theme_active')) {
-      $this->determineActiveTheme($request);
-    }
-    return $request->attributes->get('_theme_active');
-  }
-
   /**
    * {@inheritdoc}
    */
-  public function applies(Request $request) {
+  public function applies(RouteMatchInterface $route_match) {
     return TRUE;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function determineActiveTheme(Request $request) {
+  public function determineActiveTheme(RouteMatchInterface $route_match) {
     foreach ($this->getSortedNegotiators() as $negotiator) {
-      if ($negotiator->applies($request)) {
-        $theme = $negotiator->determineActiveTheme($request);
+      if ($negotiator->applies($route_match)) {
+        $theme = $negotiator->determineActiveTheme($route_match);
         if ($theme !== NULL && $this->themeAccess->checkAccess($theme)) {
-          $request->attributes->set('_theme_active', $theme);
-          return $request->attributes->get('_theme_active');
+          return $theme;
         }
       }
     }
diff --git a/core/lib/Drupal/Core/Theme/ThemeNegotiatorInterface.php b/core/lib/Drupal/Core/Theme/ThemeNegotiatorInterface.php
index e0631c2cf9fb58b64f62865fed61a1c46ed1652f..673893e709035e2788e363dc80d80fa7f9f926a7 100644
--- a/core/lib/Drupal/Core/Theme/ThemeNegotiatorInterface.php
+++ b/core/lib/Drupal/Core/Theme/ThemeNegotiatorInterface.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Theme;
 
-use Symfony\Component\HttpFoundation\Request;
+use Drupal\Core\Routing\RouteMatchInterface;
 
 /**
  * Defines an interface for classes which determine the active theme.
@@ -30,24 +30,24 @@ interface ThemeNegotiatorInterface {
   /**
    * Whether this theme negotiator should be used to set the theme.
    *
-   * @param \Symfony\Component\HttpFoundation\Request $request
-   *   The current request object.
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The current route match object.
    *
    * @return bool
    *   TRUE if this negotiator should be used or FALSE to let other negotiators
    *   decide.
    */
-  public function applies(Request $request);
+  public function applies(RouteMatchInterface $route_match);
 
   /**
    * Determine the active theme for the request.
    *
-   * @param \Symfony\Component\HttpFoundation\Request $request
-   *   The active request of the site.
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The current route match object.
    *
    * @return string|null
    *   Returns the active theme name, else return NULL.
    */
-  public function determineActiveTheme(Request $request);
+  public function determineActiveTheme(RouteMatchInterface $route_match);
 
 }
diff --git a/core/modules/block/block.module b/core/modules/block/block.module
index e029b0e8ce2ad898d2f36b0d183a652cdcac97c5..ce880be0fcce7adc52257e061b2279c5455c0cfb 100644
--- a/core/modules/block/block.module
+++ b/core/modules/block/block.module
@@ -8,7 +8,6 @@
 use Drupal\block\BlockInterface;
 use Drupal\language\Entity\Language;
 use Drupal\system\Entity\Menu;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
@@ -86,7 +85,7 @@ function block_page_build(&$page) {
 
   // Fetch a list of regions for the current theme.
   $all_regions = system_region_list($theme);
-  if (\Drupal::request()->attributes->get(RouteObjectInterface::ROUTE_NAME) != 'block.admin_demo') {
+  if (\Drupal::routeMatch()->getRouteName() != 'block.admin_demo') {
     // Load all region content assigned via blocks.
     foreach (array_keys($all_regions) as $region) {
       // Assign blocks to region.
diff --git a/core/modules/block/block.services.yml b/core/modules/block/block.services.yml
index 5373645739f6fad5e1f8b768783892b63d12cd77..e5e58d1c2bfb6c75e83949f5f1bf8dc1797c4fc7 100644
--- a/core/modules/block/block.services.yml
+++ b/core/modules/block/block.services.yml
@@ -18,6 +18,6 @@ services:
       - { name: 'event_subscriber' }
   block.node_route_context:
     class: Drupal\block\EventSubscriber\NodeRouteContext
-    arguments: ['@request_stack']
+    arguments: ['@current_route_match']
     tags:
       - { name: 'event_subscriber' }
diff --git a/core/modules/block/src/EventSubscriber/NodeRouteContext.php b/core/modules/block/src/EventSubscriber/NodeRouteContext.php
index 117bf21b74c8c4af893fe894d5116883d9b526c8..adadbe828c12d02efa086f593f931baa1bea1ce1 100644
--- a/core/modules/block/src/EventSubscriber/NodeRouteContext.php
+++ b/core/modules/block/src/EventSubscriber/NodeRouteContext.php
@@ -8,9 +8,8 @@
 namespace Drupal\block\EventSubscriber;
 
 use Drupal\Core\Plugin\Context\Context;
+use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\node\Entity\Node;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
-use Symfony\Component\HttpFoundation\RequestStack;
 
 /**
  * Sets the current node as a context on node routes.
@@ -18,36 +17,35 @@
 class NodeRouteContext extends BlockConditionContextSubscriberBase {
 
   /**
-   * The request stack.
+   * The route match object.
    *
-   * @var \Symfony\Component\HttpFoundation\RequestStack
+   * @var \Drupal\Core\Routing\RouteMatchInterface
    */
-  protected $requestStack;
+  protected $routeMatch;
 
   /**
    * Constructs a new NodeRouteContext.
    *
-   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
-   *   The request stack.
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The route match object.
    */
-  public function __construct(RequestStack $request_stack) {
-    $this->requestStack = $request_stack;
+  public function __construct(RouteMatchInterface $route_match) {
+    $this->routeMatch = $route_match;
   }
 
   /**
    * {@inheritdoc}
    */
   protected function determineBlockContext() {
-    $request = $this->requestStack->getCurrentRequest();
-    if ($request->attributes->has(RouteObjectInterface::ROUTE_OBJECT) && ($route_contexts = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)->getOption('parameters')) && isset($route_contexts['node'])) {
+    if (($route_object = $this->routeMatch->getRouteObject()) && ($route_contexts = $route_object->getOption('parameters')) && isset($route_contexts['node'])) {
       $context = new Context($route_contexts['node']);
-      if ($request->attributes->has('node')) {
-        $context->setContextValue($request->attributes->get('node'));
+      if ($node = $this->routeMatch->getParameter('node')) {
+        $context->setContextValue($node);
       }
       $this->addContext('node', $context);
     }
-    elseif ($request->attributes->get(RouteObjectInterface::ROUTE_NAME) == 'node.add') {
-      $node_type = $request->attributes->get('node_type');
+    elseif ($this->routeMatch->getRouteName() == 'node.add') {
+      $node_type = $this->routeMatch->getParameter('node_type');
       $context = new Context(array('type' => 'entity:node'));
       $context->setContextValue(Node::create(array('type' => $node_type->id())));
       $this->addContext('node', $context);
diff --git a/core/modules/block/src/Theme/AdminDemoNegotiator.php b/core/modules/block/src/Theme/AdminDemoNegotiator.php
index d175b91dd970a09ef06155a37e277f344958ec79..50d88c320f9077d9a9f1491b776e763f5be41707 100644
--- a/core/modules/block/src/Theme/AdminDemoNegotiator.php
+++ b/core/modules/block/src/Theme/AdminDemoNegotiator.php
@@ -7,9 +7,8 @@
 
 namespace Drupal\block\Theme;
 
+use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Theme\ThemeNegotiatorInterface;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
-use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Negotiates the theme for the block admin demo page via the URL.
@@ -19,17 +18,17 @@ class AdminDemoNegotiator implements ThemeNegotiatorInterface {
   /**
    * {@inheritdoc}
    */
-  public function applies(Request $request) {
-    return $request->attributes->get(RouteObjectInterface::ROUTE_NAME) == 'block.admin_demo';
+  public function applies(RouteMatchInterface $route_match) {
+    return $route_match->getRouteName() == 'block.admin_demo';
   }
 
   /**
    * {@inheritdoc}
    */
-  public function determineActiveTheme(Request $request) {
+  public function determineActiveTheme(RouteMatchInterface $route_match) {
     // We return exactly what was passed in, to guarantee that the page will
     // always be displayed using the theme whose blocks are being configured.
-    return $request->attributes->get('theme');
+    return $route_match->getParameter('theme');
   }
 
 }
diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index af041fc4a19ba523abcaaa359ae3f542f17e44d2..941c112d9f659c375ccc7fdc036c903a399d0d78 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -110,8 +110,7 @@ function forum_menu_local_tasks(&$data, $route_name) {
 
   // Add action link to 'node/add/forum' on 'forum' sub-pages.
   if (in_array($route_name, array('forum.index', 'forum.page'))) {
-    $request = \Drupal::request();
-    $forum_term = $request->attributes->get('taxonomy_term');
+    $forum_term = \Drupal::routeMatch()->getParameter('taxonomy_term');
     $vid = \Drupal::config('forum.settings')->get('vocabulary');
     $links = array();
     // Loop through all bundles for forum taxonomy vocabulary field.
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 09aff4fdb62d33c48c8c30e413a699913cfef9fa..28c826fc077e6511619660a5c27a7a2c5c67e884 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -28,7 +28,6 @@
 use Drupal\Core\Template\Attribute;
 use Drupal\file\Entity\File;
 use Drupal\language\Entity\Language as LanguageEntity;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Drupal\block\Entity\Block;
 use Drupal\Core\Session\AccountInterface;
 
@@ -559,9 +558,9 @@ function node_revision_delete($revision_id) {
  *   The ID of the node if this is a full page view, otherwise FALSE.
  */
 function node_is_page(NodeInterface $node) {
-  $request = \Drupal::request();
-  if ($request->attributes->get(RouteObjectInterface::ROUTE_NAME) == 'node.view') {
-    $page_node = $request->attributes->get('node');
+  $route_match = \Drupal::routeMatch();
+  if ($route_match->getRouteName() == 'node.view') {
+    $page_node = $route_match->getParameter('node');
   }
   return (!empty($page_node) ? $page_node->id() == $node->id() : FALSE);
 }
@@ -571,7 +570,7 @@ function node_is_page(NodeInterface $node) {
  */
 function node_preprocess_html(&$variables) {
   // If on an individual node page, add the node type to body classes.
-  if (($node = \Drupal::request()->attributes->get('node')) && $node instanceof NodeInterface) {
+  if (($node = \Drupal::routeMatch()->getParameter('node')) && $node instanceof NodeInterface) {
     $variables['attributes']['class'][] = drupal_html_class('node--type-' . $node->getType());
   }
 }
diff --git a/core/modules/rdf/rdf.module b/core/modules/rdf/rdf.module
index a66af78b5f5285bd7f97e5aef8d39d512ae3c0cc..e300e80f8251e39d58a1897d4c0e0ce541bd017c 100644
--- a/core/modules/rdf/rdf.module
+++ b/core/modules/rdf/rdf.module
@@ -7,7 +7,6 @@
 
 use Drupal\Component\Utility\String;
 use Drupal\Core\Template\Attribute;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
@@ -353,7 +352,7 @@ function rdf_preprocess_user(&$variables) {
   }
   // If we are on the user account page, add the relationship between the
   // sioc:UserAccount and the foaf:Person who holds the account.
-  if (\Drupal::request()->attributes->get(RouteObjectInterface::ROUTE_NAME) == $uri->getRouteName()) {
+  if (\Drupal::routeMatch()->getRouteName() == $uri->getRouteName()) {
     // Adds the markup for username as language neutral literal, see
     // rdf_preprocess_username().
     $name_mapping = $mapping->getPreparedFieldMapping('name');
diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module
index 0225e25371003826b5de20dfdb3504c9bea69b75..6b516d1b889da73ab1c6dcb03896115773e1facf 100644
--- a/core/modules/shortcut/shortcut.module
+++ b/core/modules/shortcut/shortcut.module
@@ -11,7 +11,6 @@
 use Drupal\Core\Url;
 use Drupal\shortcut\Entity\ShortcutSet;
 use Drupal\shortcut\ShortcutSetInterface;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
@@ -343,10 +342,8 @@ function shortcut_preprocess_page(&$variables) {
   // shortcuts and if the page's actual content is being shown (for example,
   // we do not want to display it on "access denied" or "page not found"
   // pages).
-  // Load the router item corresponding to the current page.
-  $request = \Drupal::request();
   $item = array();
-  if ($route = $request->attributes->get(RouteObjectInterface::ROUTE_NAME)) {
+  if (\Drupal::routeMatch()->getRouteObject()) {
     // @todo What should be done on a 404/403 page?
     $item['access'] = TRUE;
   }
diff --git a/core/modules/system/src/Controller/BatchController.php b/core/modules/system/src/Controller/BatchController.php
index 574872e05983075663721fbc28ac783ccfff0156..d14f9870b0ceae433fe4c74c272df1f115c60ec7 100644
--- a/core/modules/system/src/Controller/BatchController.php
+++ b/core/modules/system/src/Controller/BatchController.php
@@ -11,7 +11,6 @@
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Page\DefaultHtmlFragmentRenderer;
 use Drupal\Core\Page\HtmlPage;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
@@ -99,8 +98,7 @@ public function batchPage(Request $request) {
    */
   public function render(array $output, $status_code = 200) {
     if (!isset($output['#title'])) {
-      $request = \Drupal::request();
-      $output['#title'] = $this->titleResolver->getTitle($request, $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT));
+      $output['#title'] = $this->titleResolver->getTitle(\Drupal::request(), \Drupal::routeMatch()->getRouteObject());
     }
     $page = new HtmlPage('', isset($output['#cache']) ? $output['#cache'] : array(), $output['#title']);
 
diff --git a/core/modules/system/src/Theme/BatchNegotiator.php b/core/modules/system/src/Theme/BatchNegotiator.php
index 700250415a049ec568a14ba0338fffe62daa6983..b8e1d472b01553db69b90a57377ede508e5299fb 100644
--- a/core/modules/system/src/Theme/BatchNegotiator.php
+++ b/core/modules/system/src/Theme/BatchNegotiator.php
@@ -8,9 +8,9 @@
 namespace Drupal\system\Theme;
 
 use Drupal\Core\Batch\BatchStorageInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Theme\ThemeNegotiatorInterface;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
-use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
 
 /**
  * Sets the active theme for the batch page.
@@ -24,28 +24,39 @@ class BatchNegotiator implements ThemeNegotiatorInterface {
    */
   protected $batchStorage;
 
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
   /**
    * Constructs a BatchNegotiator.
    *
    * @param \Drupal\Core\Batch\BatchStorageInterface $batch_storage
    *   The batch storage.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack used to retrieve the current request.
    */
-  public function __construct(BatchStorageInterface $batch_storage) {
+  public function __construct(BatchStorageInterface $batch_storage, RequestStack $request_stack) {
     $this->batchStorage = $batch_storage;
+    $this->requestStack = $request_stack;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function applies(Request $request) {
-    return $request->attributes->get(RouteObjectInterface::ROUTE_NAME) == 'system.batch_page';
+  public function applies(RouteMatchInterface $route_match) {
+    return $route_match->getRouteName() == 'system.batch_page';
   }
 
   /**
    * {@inheritdoc}
    */
-  public function determineActiveTheme(Request $request) {
+  public function determineActiveTheme(RouteMatchInterface $route_match) {
     // Retrieve the current state of the batch.
+    $request = $this->requestStack->getCurrentRequest();
     $batch = &batch_get();
     if (!$batch && $request->request->has('id')) {
       $batch = $this->batchStorage->load($request->request->get('id'));
diff --git a/core/modules/system/system.services.yml b/core/modules/system/system.services.yml
index 8d09004705c827de8fe8594d1acee8f876d4098b..2b7046cb0ec0e4da10402fc7aa4d0ec641b74e69 100644
--- a/core/modules/system/system.services.yml
+++ b/core/modules/system/system.services.yml
@@ -21,7 +21,7 @@ services:
       - { name: event_subscriber }
   theme.negotiator.system.batch:
     class: Drupal\system\Theme\BatchNegotiator
-    arguments: ['@batch.storage']
+    arguments: ['@batch.storage', '@request_stack']
     tags:
       - { name: theme_negotiator, priority: 1000 }
   system.config_subscriber:
diff --git a/core/modules/system/tests/modules/menu_test/menu_test.module b/core/modules/system/tests/modules/menu_test/menu_test.module
index f8aff5db4a10999f2df180493d1f5b41615e61fb..9f1d06d2688598fa8d11d4837e46135f841c1013 100644
--- a/core/modules/system/tests/modules/menu_test/menu_test.module
+++ b/core/modules/system/tests/modules/menu_test/menu_test.module
@@ -139,7 +139,7 @@ function menu_test_theme_page_callback($inherited = FALSE) {
   // Initialize the theme system so that $theme_key will be populated.
   drupal_theme_initialize();
   // Now we check what the theme negotiator service returns.
-  $active_theme = \Drupal::service('theme.negotiator')->getActiveTheme('getActiveTheme');
+  $active_theme = \Drupal::service('theme.negotiator')->determineActiveTheme(\Drupal::routeMatch());
   $output = "Active theme: $active_theme. Actual theme: $theme_key.";
   if ($inherited) {
     $output .= ' Theme negotiation inheritance is being tested.';
diff --git a/core/modules/system/tests/modules/menu_test/src/Theme/TestThemeNegotiator.php b/core/modules/system/tests/modules/menu_test/src/Theme/TestThemeNegotiator.php
index 77954a35bd91a891ce1a9e9df879afb92b1a9141..3d3a17ee5b49c1bcb5646f0b669c8a3f7cdb88ed 100644
--- a/core/modules/system/tests/modules/menu_test/src/Theme/TestThemeNegotiator.php
+++ b/core/modules/system/tests/modules/menu_test/src/Theme/TestThemeNegotiator.php
@@ -7,8 +7,8 @@
 
 namespace Drupal\menu_test\Theme;
 
+use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Theme\ThemeNegotiatorInterface;
-use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Tests the theme negotiation functionality.
@@ -21,15 +21,15 @@ class TestThemeNegotiator implements ThemeNegotiatorInterface {
   /**
    * {@inheritdoc}
    */
-  public function applies(Request $request) {
-    return $request->attributes->has('inherited');
+  public function applies(RouteMatchInterface $route_match) {
+    return (bool) $route_match->getParameter('inherited');
   }
 
   /**
    * {@inheritdoc}
    */
-  public function determineActiveTheme(Request $request) {
-    $argument = $request->attributes->get('inherited');
+  public function determineActiveTheme(RouteMatchInterface $route_match) {
+    $argument = $route_match->getParameter('inherited');
     // Test using the variable administrative theme.
     if ($argument == 'use-admin-theme') {
       return \Drupal::config('system.theme')->get('admin');
diff --git a/core/modules/system/tests/modules/theme_test/src/Theme/CustomThemeNegotiator.php b/core/modules/system/tests/modules/theme_test/src/Theme/CustomThemeNegotiator.php
index 33b9bec067a4cab7358ee494ce583c866f5a8da6..e95f57caea1d26fb236335d4fa31c20743a7bd1f 100644
--- a/core/modules/system/tests/modules/theme_test/src/Theme/CustomThemeNegotiator.php
+++ b/core/modules/system/tests/modules/theme_test/src/Theme/CustomThemeNegotiator.php
@@ -7,10 +7,8 @@
 
 namespace Drupal\theme_test\Theme;
 
+use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Theme\ThemeNegotiatorInterface;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\Routing\Route;
 
 /**
  * Just forces the 'test_theme' theme.
@@ -20,16 +18,16 @@ class CustomThemeNegotiator implements ThemeNegotiatorInterface {
   /**
    * {@inheritdoc}
    */
-  public function applies(Request $request) {
-    return (($route_object = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)) && $route_object instanceof Route && $route_object->hasOption('_custom_theme'));
+  public function applies(RouteMatchInterface $route_match) {
+    $route = $route_match->getRouteObject();
+    return ($route && $route->hasOption('_custom_theme'));
   }
 
   /**
    * {@inheritdoc}
    */
-  public function determineActiveTheme(Request $request) {
-    $route_object = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT);
-    return $route_object->getOption('_custom_theme');
+  public function determineActiveTheme(RouteMatchInterface $route_match) {
+    return $route_match->getRouteObject()->getOption('_custom_theme');
   }
 
 }
diff --git a/core/modules/system/tests/modules/theme_test/src/Theme/HighPriorityThemeNegotiator.php b/core/modules/system/tests/modules/theme_test/src/Theme/HighPriorityThemeNegotiator.php
index 91fc9365a167ccbe0da23fff28fddc0c468ac1c5..8d9326754dca3227197de91249a48ea1129b6ec9 100644
--- a/core/modules/system/tests/modules/theme_test/src/Theme/HighPriorityThemeNegotiator.php
+++ b/core/modules/system/tests/modules/theme_test/src/Theme/HighPriorityThemeNegotiator.php
@@ -7,28 +7,25 @@
 
 namespace Drupal\theme_test\Theme;
 
+use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Theme\ThemeNegotiatorInterface;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
-use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Implements a test theme negotiator which was configured with a high priority.
  */
 class HighPriorityThemeNegotiator implements ThemeNegotiatorInterface {
 
-
-
   /**
    * {@inheritdoc}
    */
-  public function applies(Request $request) {
-    return (($route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME)) && $route_name == 'theme_test.priority');
+  public function applies(RouteMatchInterface $route_match) {
+    return ($route_match->getRouteName() == 'theme_test.priority');
   }
 
   /**
    * {@inheritdoc}
    */
-  public function determineActiveTheme(Request $request) {
+  public function determineActiveTheme(RouteMatchInterface $route_match) {
     return 'stark';
   }
 
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index fde291e76ef52c34ce55d6445a452e380343b2a3..cf9a6abde1a4e15c7c8e94c0678a1d766f48478c 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -332,9 +332,7 @@ function template_preprocess_taxonomy_term(&$variables) {
  *   A taxonomy term entity.
  */
 function taxonomy_term_is_page(Term $term) {
-  $request = \Drupal::request();
-  if ($request->attributes->has('taxonomy_term')) {
-    $page_term = $request->attributes->get('taxonomy_term');
+  if ($page_term = \Drupal::routeMatch()->getParameter('taxonomy_term')) {
     return $page_term->id() == $term->id();
   }
   return FALSE;
diff --git a/core/modules/tour/tour.module b/core/modules/tour/tour.module
index ffe79c2dd52860da96448021dd0336d89853e5e1..f6774fdf6c9a4a231334bedad9d11805368aceb9 100644
--- a/core/modules/tour/tour.module
+++ b/core/modules/tour/tour.module
@@ -5,7 +5,6 @@
  * Main functions of the module.
  */
 use Drupal\Core\Cache\CacheBackendInterface;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
@@ -83,8 +82,8 @@ function tour_preprocess_page(&$variables) {
   }
 
   // Load all of the items and match on route name.
-  $request = \Drupal::request();
-  $route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME);
+  $route_match = \Drupal::routeMatch();
+  $route_name = $route_match->getRouteName();
 
   $results = \Drupal::entityQuery('tour')
     ->condition('routes.*.route_name', $route_name)
@@ -92,7 +91,7 @@ function tour_preprocess_page(&$variables) {
   if (!empty($results) && $tours = entity_load_multiple('tour', array_keys($results))) {
     foreach ($tours as $id => $tour) {
       // Match on params.
-      if (!$tour->hasMatchingRoute($route_name, $request->attributes->get('_raw_variables')->all())) {
+      if (!$tour->hasMatchingRoute($route_name, $route_match->getRawParameters()->all())) {
         unset($tours[$id]);
       }
     }
diff --git a/core/modules/user/src/Theme/AdminNegotiator.php b/core/modules/user/src/Theme/AdminNegotiator.php
index ce6f04bc6251f64b77594817370a13a9590244d5..9bb942ab2d1ebe5d02f74b96060932a0a991aa09 100644
--- a/core/modules/user/src/Theme/AdminNegotiator.php
+++ b/core/modules/user/src/Theme/AdminNegotiator.php
@@ -10,10 +10,9 @@
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Routing\AdminContext;
+use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Theme\ThemeNegotiatorInterface;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
-use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Sets the active theme on admin pages.
@@ -68,14 +67,14 @@ public function __construct(AccountInterface $user, ConfigFactoryInterface $conf
   /**
    * {@inheritdoc}
    */
-  public function applies(Request $request) {
-    return ($this->entityManager->hasController('user_role', 'storage') && $this->user->hasPermission('view the administration theme') && $this->adminContext->isAdminRoute($request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)));
+  public function applies(RouteMatchInterface $route_match) {
+    return ($this->entityManager->hasController('user_role', 'storage') && $this->user->hasPermission('view the administration theme') && $this->adminContext->isAdminRoute($route_match->getRouteObject()));
   }
 
   /**
    * {@inheritdoc}
    */
-  public function determineActiveTheme(Request $request) {
+  public function determineActiveTheme(RouteMatchInterface $route_match) {
     return $this->configFactory->get('system.theme')->get('admin');
   }
 
diff --git a/core/tests/Drupal/Tests/Core/Routing/CurrentRouteMatchTest.php b/core/tests/Drupal/Tests/Core/Routing/CurrentRouteMatchTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9deddd5d2c0a9d14c78154f384ac701d9ab0f8f3
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Routing/CurrentRouteMatchTest.php
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Tests\Core\Routing\RouteMatchTest.
+ */
+
+namespace Drupal\Tests\Core\Routing;
+
+use Drupal\Core\Routing\CurrentRouteMatch;
+use Drupal\Core\Routing\RouteMatch;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Symfony\Component\HttpFoundation\ParameterBag;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Unit tests for CurrentRouteMatch.
+ *
+ * @coversDefaultClass \Drupal\Core\Routing\CurrentRouteMatch
+ */
+class CurrentRouteMatchTest extends RouteMatchBaseTest {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'CurrentRouteMatch',
+      'description' => 'Unit tests for CurrentRouteMatch.',
+      'group' => 'Routing',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getRouteMatch($name, Route $route, array $parameters, array $raw_parameters) {
+    $request_stack = new RequestStack();
+    $request = new Request();
+    $request_stack->push($request);
+
+    $request = $request_stack->getCurrentRequest();
+    $request->attributes = new ParameterBag($parameters);
+    $request->attributes->set(RouteObjectInterface::ROUTE_NAME, $name);
+    $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, $route);
+    $request->attributes->set('_raw_variables', new ParameterBag($raw_parameters));
+    return new CurrentRouteMatch($request_stack);
+  }
+
+  /**
+   * @covers ::__construct
+   * @covers ::getRouteObject
+   * @covers ::getCurrentRouteMatch
+   * @covers ::getRouteMatch
+   */
+  public function testGetCurrentRouteObject() {
+
+    $request_stack = new RequestStack();
+    $request = new Request();
+    $request_stack->push($request);
+    $current_route_match = new CurrentRouteMatch($request_stack);
+
+    // Before routing.
+    $this->assertNull($current_route_match->getRouteObject());
+
+    // After routing.
+    $route = new Route('/test-route/{foo}');
+    $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'test_route');
+    $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, $route);
+    $request->attributes->set('foo', '1');
+    $this->assertSame('1', $current_route_match->getParameter('foo'));
+
+    // Immutable for the same request once a route has been matched.
+    $request->attributes->set('foo', '2');
+    $this->assertSame('1', $current_route_match->getParameter('foo'));
+
+    // Subrequest.
+    $subrequest = new Request();
+    $subrequest->attributes->set(RouteObjectInterface::ROUTE_NAME, 'test_subrequest_route');
+    $subrequest->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('/test-subrequest-route/{foo}'));
+    $subrequest->attributes->set('foo', '2');
+    $request_stack->push($subrequest);
+    $this->assertSame('2', $current_route_match->getParameter('foo'));
+
+    // Restored original request.
+    $request_stack->pop();
+    $this->assertSame('1', $current_route_match->getParameter('foo'));
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Routing/RouteMatchBaseTest.php b/core/tests/Drupal/Tests/Core/Routing/RouteMatchBaseTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..57b03e3370b0d079ed6a5bb7f266b39cb3ba02c4
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Routing/RouteMatchBaseTest.php
@@ -0,0 +1,149 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Tests\Core\Routing\RouteMatchTest.
+ */
+
+namespace Drupal\Tests\Core\Routing;
+
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Symfony\Component\HttpFoundation\ParameterBag;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Base test class for testing the RouteMatch object.
+ */
+abstract class RouteMatchBaseTest extends UnitTestCase {
+
+  /**
+   * Build a test route match object for the given implementation.
+   *
+   * @param $name
+   *   Route name.
+   * @param Route $route
+   *   Request object
+   * @param array $parameters
+   *   Parameters array
+   * @param $raw_parameters
+   *   Raw parameters array
+   * @return \Drupal\Core\Routing\RouteMatchInterface
+   */
+  abstract protected function getRouteMatch($name, Route $route, array $parameters, array $raw_parameters);
+
+
+  /**
+   * Provide sets of parameters and expected parameters for parameter tests.
+   */
+  public function routeMatchProvider() {
+    $base_data = array(
+      array(
+        new Route(
+          '/test-route/{param_without_leading_underscore}/{_param_with_leading_underscore}',
+          array(
+            'default_without_leading_underscore' => NULL,
+            '_default_with_leading_underscore' => NULL,
+          )
+        ),
+        array(
+          'param_without_leading_underscore' => 'value',
+          '_param_with_leading_underscore' => 'value',
+          'default_without_leading_underscore' => 'value',
+          '_default_with_leading_underscore' => 'value',
+          'foo' => 'value',
+        ),
+        // Parameters should be filtered to only those defined by the route.
+        // Specifically:
+        // - Path parameters, regardless of name.
+        // - Defaults that are not path parameters only if they do not start with
+        //   an underscore.
+        array(
+          'param_without_leading_underscore' => 'value',
+          '_param_with_leading_underscore' => 'value',
+          'default_without_leading_underscore' => 'value',
+        ),
+      ),
+    );
+
+    $data = array();
+    foreach ($base_data as $entry) {
+      $route = $entry[0];
+      $params = $entry[1];
+      $expected_params = $entry[2];
+      $data[] = array(
+        $this->getRouteMatch('test_route', $route, $params, $params),
+        $route,
+        $params,
+        $expected_params,
+      );
+    }
+
+    return $data;
+  }
+
+  /**
+   * @covers ::getRouteName
+   * @dataProvider routeMatchProvider
+   */
+  public function testGetRouteName(RouteMatchInterface $route_match) {
+    $this->assertSame('test_route', $route_match->getRouteName());
+  }
+
+  /**
+   * @covers ::getRouteObject
+   * @dataProvider routeMatchProvider
+   */
+  public function testGetRouteObject(RouteMatchInterface $route_match, Route $route) {
+    $this->assertSame($route, $route_match->getRouteObject());
+  }
+
+  /**
+   * @covers ::getParameter
+   * @covers \Drupal\Core\Routing\RouteMatch::getParameterNames
+   * @dataProvider routeMatchProvider
+   */
+  public function testGetParameter(RouteMatchInterface $route_match, Route $route, $parameters, $expected_filtered_parameters) {
+    foreach ($expected_filtered_parameters as $name => $expected_value) {
+      $this->assertSame($expected_value, $route_match->getParameter($name));
+    }
+    foreach (array_diff_key($parameters, $expected_filtered_parameters) as $name) {
+      $this->assertNull($route_match->getParameter($name));
+    }
+  }
+
+  /**
+   * @covers ::getParameters
+   * @covers \Drupal\Core\Routing\RouteMatch::getParameterNames
+   * @dataProvider routeMatchProvider
+   */
+  public function testGetParameters(RouteMatchInterface $route_match, Route $route, $parameters, $expected_filtered_parameters) {
+    $this->assertSame($expected_filtered_parameters, $route_match->getParameters()->all());
+  }
+
+  /**
+   * @covers ::getRawParameter
+   * @covers \Drupal\Core\Routing\RouteMatch::getParameterNames
+   * @dataProvider routeMatchProvider
+   */
+  public function testGetRawParameter(RouteMatchInterface $route_match, Route $route, $parameters, $expected_filtered_parameters) {
+    foreach ($expected_filtered_parameters as $name => $expected_value) {
+      $this->assertSame($expected_value, $route_match->getRawParameter($name));
+    }
+    foreach (array_diff_key($parameters, $expected_filtered_parameters) as $name) {
+      $this->assertNull($route_match->getRawParameter($name));
+    }
+  }
+
+  /**
+   * @covers ::getRawParameters
+   * @covers \Drupal\Core\Routing\RouteMatch::getParameterNames
+   * @dataProvider routeMatchProvider
+   */
+  public function testGetRawParameters(RouteMatchInterface $route_match, Route $route, $parameters, $expected_filtered_parameters) {
+    $this->assertSame($expected_filtered_parameters, $route_match->getRawParameters()->all());
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Routing/RouteMatchTest.php b/core/tests/Drupal/Tests/Core/Routing/RouteMatchTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6a51add267bd89a43c8bf2fbfd6aacb2aee1b4fb
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Routing/RouteMatchTest.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Tests\Core\Routing\RouteMatchTest.
+ */
+
+namespace Drupal\Tests\Core\Routing;
+
+use Drupal\Core\Routing\RouteMatch;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Symfony\Component\HttpFoundation\ParameterBag;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Unit tests for RouteMatch.
+ *
+ * @coversDefaultClass \Drupal\Core\Routing\RouteMatch
+ */
+class RouteMatchTest extends RouteMatchBaseTest {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'RouteMatch',
+      'description' => 'Unit tests for RouteMatch.',
+      'group' => 'Routing',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getRouteMatch($name, Route $route, array $parameters, array $raw_parameters) {
+    return new RouteMatch($name, $route, $parameters, $raw_parameters);
+  }
+
+  /**
+   * @covers ::createFromRequest
+   * @covers ::__construct
+   * @covers \Drupal\Core\Routing\NullRouteMatch
+   */
+  public function testRouteMatchFromRequest() {
+    $request = new Request();
+
+    // A request that hasn't been routed yet.
+    $route_match = RouteMatch::createFromRequest($request);
+    $this->assertNull($route_match->getRouteName());
+    $this->assertNull($route_match->getRouteObject());
+    $this->assertSame(array(), $route_match->getParameters()->all());
+    $this->assertNull($route_match->getParameter('foo'));
+    $this->assertSame(array(), $route_match->getRawParameters()->all());
+    $this->assertNull($route_match->getRawParameter('foo'));
+
+    // A routed request without parameter upcasting.
+    $route = new Route('/test-route/{foo}');
+    $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'test_route');
+    $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, $route);
+    $request->attributes->set('foo', '1');
+    $route_match = RouteMatch::createFromRequest($request);
+    $this->assertSame('test_route', $route_match->getRouteName());
+    $this->assertSame($route, $route_match->getRouteObject());
+    $this->assertSame(array('foo' => '1'), $route_match->getParameters()->all());
+    $this->assertSame(array(), $route_match->getRawParameters()->all());
+
+    // A routed request with parameter upcasting.
+    $foo = new \stdClass();
+    $foo->value = 1;
+    $request->attributes->set('foo', $foo);
+    $request->attributes->set('_raw_variables', new ParameterBag(array('foo' => '1')));
+    $route_match = RouteMatch::createFromRequest($request);
+    $this->assertSame(array('foo' => $foo), $route_match->getParameters()->all());
+    $this->assertSame(array('foo' => '1'), $route_match->getRawParameters()->all());
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Theme/ThemeNegotiatorTest.php b/core/tests/Drupal/Tests/Core/Theme/ThemeNegotiatorTest.php
index 89a1e46358c11b27e306b08114af8acc1fe00594..4830e4757de6bdcf260b3cb6e0f143bbcaac2a06 100644
--- a/core/tests/Drupal/Tests/Core/Theme/ThemeNegotiatorTest.php
+++ b/core/tests/Drupal/Tests/Core/Theme/ThemeNegotiatorTest.php
@@ -7,10 +7,12 @@
 
 namespace Drupal\Tests\Core\Theme;
 
+use Drupal\Core\Routing\RouteMatch;
 use Drupal\Core\Theme\ThemeNegotiator;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\Routing\Route;
 
 /**
  * Tests the theme negotiator.
@@ -54,8 +56,7 @@ protected function setUp() {
     $this->themeAccessCheck = $this->getMockBuilder('\Drupal\Core\Theme\ThemeAccessCheck')
       ->disableOriginalConstructor()
       ->getMock();
-    $this->requestStack = new RequestStack();
-    $this->themeNegotiator = new ThemeNegotiator($this->themeAccessCheck, $this->requestStack);
+    $this->themeNegotiator = new ThemeNegotiator($this->themeAccessCheck);
   }
 
   /**
@@ -78,11 +79,10 @@ public function testDetermineActiveTheme() {
       ->method('checkAccess')
       ->will($this->returnValue(TRUE));
 
-    $request = Request::create('/test-route');
-    $theme = $this->themeNegotiator->determineActiveTheme($request);
+    $route_match = new RouteMatch('test_route', new Route('/test-route'), array(), array());
+    $theme = $this->themeNegotiator->determineActiveTheme($route_match);
 
     $this->assertEquals('example_test', $theme);
-    $this->assertEquals('example_test', $request->attributes->get('_theme_active'));
   }
 
   /**
@@ -113,11 +113,10 @@ public function testDetermineActiveThemeWithPriority() {
       ->method('checkAccess')
       ->will($this->returnValue(TRUE));
 
-    $request = Request::create('/test-route');
-    $theme = $this->themeNegotiator->determineActiveTheme($request);
+    $route_match = new RouteMatch('test_route', new Route('/test-route'), array(), array());
+    $theme = $this->themeNegotiator->determineActiveTheme($route_match);
 
     $this->assertEquals('example_test', $theme);
-    $this->assertEquals('example_test', $request->attributes->get('_theme_active'));
   }
 
   /**
@@ -156,11 +155,10 @@ public function testDetermineActiveThemeWithAccessCheck() {
       ->with('example_test2')
       ->will($this->returnValue(TRUE));
 
-    $request = Request::create('/test-route');
-    $theme = $this->themeNegotiator->determineActiveTheme($request);
+    $route_match = new RouteMatch('test_route', new Route('/test-route'), array(), array());
+    $theme = $this->themeNegotiator->determineActiveTheme($route_match);
 
     $this->assertEquals('example_test2', $theme);
-    $this->assertEquals('example_test2', $request->attributes->get('_theme_active'));
   }
 
   /**
@@ -192,11 +190,10 @@ public function testDetermineActiveThemeWithNotApplyingNegotiator() {
       ->method('checkAccess')
       ->will($this->returnValue(TRUE));
 
-    $request = Request::create('/test-route');
-    $theme = $this->themeNegotiator->determineActiveTheme($request);
+    $route_match = new RouteMatch('test_route', new Route('/test-route'), array(), array());
+    $theme = $this->themeNegotiator->determineActiveTheme($route_match);
 
     $this->assertEquals('example_test2', $theme);
-    $this->assertEquals('example_test2', $request->attributes->get('_theme_active'));
   }
 
 }