diff --git a/core/core.services.yml b/core/core.services.yml
index 0fca40adeb73d30ac2b3587c5f6ca9c9ad0f5be2..6f8cce6566a6b506957794f4754e46437f5d442b 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -293,13 +293,25 @@ services:
       - { name: route_filter }
   paramconverter_manager:
     class: Drupal\Core\ParamConverter\ParamConverterManager
+    calls:
+      - [setContainer, ['@service_container']]
     tags:
       - { name: route_enhancer }
+  paramconverter_subscriber:
+    class: Drupal\Core\EventSubscriber\ParamConverterSubscriber
+    tags:
+      - { name: event_subscriber }
+    arguments: ['@paramconverter_manager']
   paramconverter.entity:
     class: Drupal\Core\ParamConverter\EntityConverter
     tags:
       - { name: paramconverter }
     arguments: ['@plugin.manager.entity']
+  route_subscriber.entity:
+    class: Drupal\Core\EventSubscriber\EntityRouteAlterSubscriber
+    tags:
+      - { name: event_subscriber }
+    arguments: ['@plugin.manager.entity']
   reverse_proxy_subscriber:
     class: Drupal\Core\EventSubscriber\ReverseProxySubscriber
     tags:
diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php
index 68f5a97ba6da2b1e95f157c7791c7380bce1e1b2..a6cb9574da9bb537209484ab7c47530e92eb5aaa 100644
--- a/core/lib/Drupal/Core/CoreServiceProvider.php
+++ b/core/lib/Drupal/Core/CoreServiceProvider.php
@@ -55,7 +55,7 @@ public function register(ContainerBuilder $container) {
     // Add a compiler pass for registering event subscribers.
     $container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING);
     $container->addCompilerPass(new RegisterAccessChecksPass());
-    // Add a compiler pass for upcasting of entity route parameters.
+    // Add a compiler pass for upcasting route parameters.
     $container->addCompilerPass(new RegisterParamConvertersPass());
     $container->addCompilerPass(new RegisterRouteEnhancersPass());
     // Add a compiler pass for registering services needing destruction.
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterParamConvertersPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterParamConvertersPass.php
index 6fe1447bc588208f8a517839b28f6ba31af2d3f7..1948727b2b2d96161cb981a2fe39c8d90f8d0393 100644
--- a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterParamConvertersPass.php
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterParamConvertersPass.php
@@ -23,26 +23,14 @@ class RegisterParamConvertersPass implements CompilerPassInterface {
    *   The container to process.
    */
   public function process(ContainerBuilder $container) {
-
     if (!$container->hasDefinition('paramconverter_manager')) {
       return;
     }
 
     $manager = $container->getDefinition('paramconverter_manager');
-
-    $services = array();
     foreach ($container->findTaggedServiceIds('paramconverter') as $id => $attributes) {
       $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
-
-      $services[$priority][] = new Reference($id);
-    }
-
-    krsort($services);
-
-    foreach ($services as $priority) {
-      foreach ($priority as $service) {
-        $manager->addMethodCall('addConverter', array($service));
-      }
+      $manager->addMethodCall('addConverter', array($id, $priority));
     }
   }
 }
diff --git a/core/lib/Drupal/Core/EventSubscriber/EntityRouteAlterSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/EntityRouteAlterSubscriber.php
new file mode 100644
index 0000000000000000000000000000000000000000..53f9e3155f081934f533998e60cca4747c8e1d69
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/EntityRouteAlterSubscriber.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\EventSubscriber\EntityRouteAlterSubscriber.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Drupal\Core\Entity\EntityManager;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Drupal\Core\Routing\RoutingEvents;
+use Drupal\Core\Routing\RouteBuildEvent;
+
+/**
+ * Registers the 'type' of route parameter names that match an entity type.
+ *
+ * @todo Matching on parameter *name* is not ideal, because it breaks
+ *   encapsulation: parameter names are local to the controller and route, and
+ *   controllers and routes can't be expected to know what all possible entity
+ *   types might exist across all modules in order to pick names that don't
+ *   conflict. Instead, the 'type' should be determined from introspecting what
+ *   kind of PHP variable (e.g., a type hinted interface) the controller
+ *   requires: https://drupal.org/node/2041907.
+ */
+class EntityRouteAlterSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManager
+   */
+  protected $entityManager;
+
+  /**
+   * Constructs a new EntityRouteAlterSubscriber.
+   *
+   * @param \Drupal\Core\Entity\EntityManager $entity_manager
+   *   The entity manager.
+   */
+  public function __construct(EntityManager $entity_manager) {
+    $this->entityManager = $entity_manager;
+  }
+
+  /**
+   * Applies parameter converters to route parameters.
+   *
+   * @param \Drupal\Core\Routing\RouteBuildEvent $event
+   *   The event to process.
+   */
+  public function onRoutingRouteAlterSetType(RouteBuildEvent $event) {
+    $entity_types = array_keys($this->entityManager->getDefinitions());
+    foreach ($event->getRouteCollection() as $route) {
+      $parameter_definitions = $route->getOption('parameters') ?: array();
+      // For all route parameter names that match an entity type, add the 'type'
+      // to the parameter definition if it's not already explicitly provided.
+      foreach (array_intersect($route->compile()->getVariables(), $entity_types) as $parameter_name) {
+        if (!isset($parameter_definitions[$parameter_name])) {
+          $parameter_definitions[$parameter_name] = array();
+        }
+        $parameter_definitions[$parameter_name] += array(
+          'type' => 'entity:' . $parameter_name,
+        );
+      }
+      if (!empty($parameter_definitions)) {
+        $route->setOption('parameters', $parameter_definitions);
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  static function getSubscribedEvents() {
+    $events[RoutingEvents::ALTER][] = array('onRoutingRouteAlterSetType', 100);
+    return $events;
+  }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/ParamConverterSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ParamConverterSubscriber.php
new file mode 100644
index 0000000000000000000000000000000000000000..f9a0a6933d32df737f935fb49e4a6a47f6ed45c9
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/ParamConverterSubscriber.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\EventSubscriber\ParamConverterSubscriber.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Drupal\Core\ParamConverter\ParamConverterManager;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Drupal\Core\Routing\RoutingEvents;
+use Drupal\Core\Routing\RouteBuildEvent;
+
+/**
+ * Event subscriber for registering parameter converters with routes.
+ */
+class ParamConverterSubscriber implements EventSubscriberInterface {
+
+  /**
+   * The parameter converter manager.
+   *
+   * @var \Drupal\Core\ParamConverter\ParamConverterManager
+   */
+  protected $paramConverterManager;
+
+  /**
+   * Constructs a new ParamConverterSubscriber.
+   *
+   * @param \Drupal\Core\ParamConverter\ParamConverterManager $param_converter_manager
+   *   The parameter converter manager that will be responsible for upcasting
+   *   request attributes.
+   */
+  public function __construct(ParamConverterManager $param_converter_manager) {
+    $this->paramConverterManager = $param_converter_manager;
+  }
+
+  /**
+   * Applies parameter converters to route parameters.
+   *
+   * @param \Drupal\Core\Routing\RouteBuildEvent $event
+   *   The event to process.
+   */
+  public function onRoutingRouteAlterSetParameterConverters(RouteBuildEvent $event) {
+    $this->paramConverterManager->setRouteParameterConverters($event->getRouteCollection());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  static function getSubscribedEvents() {
+    $events[RoutingEvents::ALTER][] = array('onRoutingRouteAlterSetParameterConverters', 10);
+    return $events;
+  }
+}
diff --git a/core/lib/Drupal/Core/ParamConverter/EntityConverter.php b/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
index 50b7d7cb7a4abdc39c8c6e6421afbfacab01c5b2..5d637950cb8dc050c292f71e218fb09174656e06 100644
--- a/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
+++ b/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
@@ -12,8 +12,7 @@
 use Drupal\Core\Entity\EntityManager;
 
 /**
- * This class allows the upcasting of entity ids to the respective entity
- * object.
+ * Parameter converter for upcasting entity ids to full objects.
  */
 class EntityConverter implements ParamConverterInterface {
 
@@ -30,74 +29,29 @@ class EntityConverter implements ParamConverterInterface {
    * @param \Drupal\Core\Entity\EntityManager $entityManager
    *   The entity manager.
    */
-  public function __construct(EntityManager $entityManager) {
-    $this->entityManager = $entityManager;
+  public function __construct(EntityManager $entity_manager) {
+    $this->entityManager = $entity_manager;
   }
 
   /**
-   * Tries to upcast every variable to an entity type.
-   *
-   * If there is a type denoted in the route options it will try to upcast to
-   * it, if there is no definition in the options it will try to upcast to an
-   * entity type of that name. If the chosen enity type does not exists it will
-   * leave the variable untouched.
-   * If the entity type exist, but there is no entity with the given id it will
-   * convert the variable to NULL.
-   *
-   * Example:
-   *
-   * pattern: '/a/{user}/some/{foo}/and/{bar}/'
-   * options:
-   *   converters:
-   *     foo: 'node'
-   *
-   * The value for {user} will be converted to a user entity and the value
-   * for {foo} to a node entity, but it will not touch the value for {bar}.
-   *
-   * It will not process variables which are marked as converted. It will mark
-   * any variable it processes as converted.
-   *
-   * @param array &$variables
-   *   Array of values to convert to their corresponding objects, if applicable.
-   * @param \Symfony\Component\Routing\Route $route
-   *   The route object.
-   * @param array &$converted
-   *   Array collecting the names of all variables which have been
-   *   altered by a converter.
+   * {@inheritdoc}
    */
-  public function process(array &$variables, Route $route, array &$converted) {
-    $variable_names = $route->compile()->getVariables();
-
-    $options = $route->getOptions();
-    $configuredTypes = isset($options['converters']) ? $options['converters'] : array();
-
-    $entityTypes = array_keys($this->entityManager->getDefinitions());
-
-    foreach ($variable_names as $name) {
-      // Do not process this variable if it's already marked as converted.
-      if (in_array($name, $converted)) {
-        continue;
-      }
-
-      // Obtain entity type to convert to from the route configuration or just
-      // use the variable name as default.
-      if (array_key_exists($name, $configuredTypes)) {
-        $type = $configuredTypes[$name];
-      }
-      else {
-        $type = $name;
-      }
-
-      if (in_array($type, $entityTypes)) {
-        $value = $variables[$name];
-
-        $storageController = $this->entityManager->getStorageController($type);
-        $entity = $storageController->load($value);
-        $variables[$name] = $entity;
+  public function convert($value, $definition, $name, array $defaults, Request $request) {
+    $entity_type = substr($definition['type'], strlen('entity:'));
+    if ($storage = $this->entityManager->getStorageController($entity_type)) {
+      return $storage->load($value);
+    }
+  }
 
-        // Mark this variable as converted.
-        $converted[] = $name;
-      }
+  /**
+   * {@inheritdoc}
+   */
+  public function applies($definition, $name, Route $route) {
+    if (!empty($definition['type']) && strpos($definition['type'], 'entity:') === 0) {
+      $entity_type = substr($definition['type'], strlen('entity:'));
+      return (bool) $this->entityManager->getDefinition($entity_type);
     }
+    return FALSE;
   }
+
 }
diff --git a/core/lib/Drupal/Core/ParamConverter/ParamConverterInterface.php b/core/lib/Drupal/Core/ParamConverter/ParamConverterInterface.php
index 74307c2762cbb610bc3ffb0d30eaa76278aea8d9..957dfd955cd661d66df1cf63fe5dcba467a2165c 100644
--- a/core/lib/Drupal/Core/ParamConverter/ParamConverterInterface.php
+++ b/core/lib/Drupal/Core/ParamConverter/ParamConverterInterface.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\ParamConverter;
 
+use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Route;
 
 /**
@@ -17,13 +18,36 @@ interface ParamConverterInterface {
   /**
    * Allows to convert variables to their corresponding objects.
    *
-   * @param array &$variables
-   *   Array of values to convert to their corresponding objects, if applicable.
+   * @param mixed $value
+   *   The raw value.
+   * @param mixed $definition
+   *   The parameter definition provided in the route options.
+   * @param string $name
+   *   The name of the parameter.
+   * @param array $defaults
+   *   The route defaults array.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request object.
+   *
+   * @return mixed|null
+   *   The converted parameter value.
+   */
+  public function convert($value, $definition, $name, array $defaults, Request $request);
+
+  /**
+   * Determines if the converter applies to a specific route and variable.
+   *
+   * @param mixed $definition
+   *   The parameter definition provided in the route options.
+   * @param string $name
+   *   The name of the parameter.
    * @param \Symfony\Component\Routing\Route $route
-   *   The route object.
-   * @param array &$converted
-   *   Array collecting the names of all variables which have been
-   *   altered by a converter.
+   *   The route to consider attaching to.
+   *
+   * @return bool
+   *   TRUE if the converter applies to the passed route and parameter, FALSE
+   *   otherwise.
    */
-  public function process(array &$variables, Route $route, array &$converted);
+  public function applies($definition, $name, Route $route);
+
 }
diff --git a/core/lib/Drupal/Core/ParamConverter/ParamConverterManager.php b/core/lib/Drupal/Core/ParamConverter/ParamConverterManager.php
index be5676e12f529858b1c55f67eebd9dbf13dc5278..0b494d8b749ba58dc9b0e7a24411144cf0b962cf 100644
--- a/core/lib/Drupal/Core/ParamConverter/ParamConverterManager.php
+++ b/core/lib/Drupal/Core/ParamConverter/ParamConverterManager.php
@@ -11,79 +11,175 @@
 use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\Routing\RouteCollection;
 use Symfony\Component\HttpFoundation\Request;
 
-use Drupal\Core\ParamConverter\ParamConverterInterface;
-
 /**
- * Provides a service which allows to enhance (say alter) the arguments coming
- * from the URL.
- *
- * A typical use case for this would be upcasting a node id to a node entity.
+ * Manages converter services for converting request parameters to full objects.
  *
- * This class will not enhance any of the arguments itself, but allow other
- * services to register to do so.
+ * A typical use case for this would be upcasting (converting) a node id to a
+ * node entity.
  */
-class ParamConverterManager implements RouteEnhancerInterface {
+class ParamConverterManager extends ContainerAware implements RouteEnhancerInterface {
+
+  /**
+   * An array of registered converter service ids.
+   *
+   * @var array
+   */
+  protected $converterIds = array();
+
+  /**
+   * Array of registered converter service ids sorted by their priority.
+   *
+   * @var array
+   */
+  protected $sortedConverterIds;
 
   /**
-   * Converters managed by the ParamConverterManager.
+   * Array of loaded converter services keyed by their ids.
    *
    * @var array
    */
-  protected $converters;
+  protected $converters = array();
 
   /**
-   * Adds a converter to the paramconverter service.
+   * Registers a parameter converter with the manager.
    *
-   * @see \Drupal\Core\DependencyInjection\Compiler\RegisterParamConvertersPass
+   * @param string $converter
+   *   The parameter converter service id to register.
+   * @param int $priority
+   *   (optional) The priority of the converter. Defaults to 0.
    *
-   * @param \Drupal\Core\ParamConverter\ParamConverterInterface $converter
-   *   The converter to add.
+   * @return \Drupal\Core\ParamConverter\ParamConverterManager
+   *   The called object for chaining.
    */
-  public function addConverter(ParamConverterInterface $converter) {
-    $this->converters[] = $converter;
+  public function addConverter($converter, $priority = 0) {
+    if (empty($this->converterIds[$priority])) {
+      $this->converterIds[$priority] = array();
+    }
+    $this->converterIds[$priority][] = $converter;
+    unset($this->sortedConverterIds);
     return $this;
   }
 
   /**
-   * Implements \Symfony\Cmf\Component\Routing\Enhancer\Å–outeEnhancerIterface.
+   * Sorts the converter service ids and flattens them.
+   *
+   * @return array
+   *   The sorted parameter converter service ids.
+   */
+  public function getConverterIds() {
+    if (!isset($this->sortedConverterIds)) {
+      krsort($this->converterIds);
+      $this->sortedConverterIds = array();
+      foreach ($this->converterIds as $resolvers) {
+        $this->sortedConverterIds = array_merge($this->sortedConverterIds, $resolvers);
+      }
+    }
+    return $this->sortedConverterIds;
+  }
+
+  /**
+   * Lazy-loads converter services.
+   *
+   * @param string $converter
+   *   The service id of converter service to load.
+   *
+   * @return \Drupal\Core\ParamConverter\ParamConverterInterface
+   *   The loaded converter service identified by the given service id.
+   *
+   * @throws \InvalidArgumentException
+   *   If the given service id is not a registered converter.
+   */
+  public function getConverter($converter) {
+    if (isset($this->converters[$converter])) {
+      return $this->converters[$converter];
+    }
+    if (!in_array($converter, $this->getConverterIds())) {
+      throw new \InvalidArgumentException(sprintf('No converter has been registered for %s', $converter));
+    }
+    return $this->converters[$converter] = $this->container->get($converter);
+  }
+
+  /**
+   * Saves a list of applicable converters to each route.
    *
-   * Iterates over all registered converters and allows them to alter the
-   * defaults.
+   * @param \Symfony\Component\Routing\RouteCollection $routes
+   *   A collection of routes to apply converters to.
+   */
+  public function setRouteParameterConverters(RouteCollection $routes) {
+    foreach ($routes->all() as $route) {
+      if (!$parameters = $route->getOption('parameters')) {
+        // Continue with the next route if no parameters have been defined.
+        continue;
+      }
+
+      // Loop over all defined parameters and look up the right converter.
+      foreach ($parameters as $name => &$definition) {
+        if (isset($definition['converter'])) {
+          // Skip parameters that already have a manually set converter.
+          continue;
+        }
+
+        foreach ($this->getConverterIds() as $converter) {
+          if ($this->getConverter($converter)->applies($definition, $name, $route)) {
+            $definition['converter'] = $converter;
+            break;
+          }
+        }
+      }
+
+      // Override the parameters array.
+      $route->setOption('parameters', $parameters);
+    }
+  }
+
+  /**
+   * Invokes the registered converter for each defined parameter on a route.
    *
    * @param array $defaults
-   *   The getRouteDefaults array.
+   *   The route defaults array.
    * @param \Symfony\Component\HttpFoundation\Request $request
    *   The current request.
    *
+   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
+   *   If one of the assigned converters returned NULL because the given
+   *   variable could not be converted.
+   *
    * @return array
    *   The modified defaults.
    */
   public function enhance(array $defaults, Request $request) {
-    // This array will collect the names of all variables which have been
-    // altered by a converter.
-    // This serves two purposes:
-    // 1. It might prevent converters later in the pipeline to process
-    //    a variable again.
-    // 2. To check if upcasting was successfull after each converter had
-    //    a go. See below.
-    $converters = array();
-
     $route = $defaults[RouteObjectInterface::ROUTE_OBJECT];
 
-    foreach ($this->converters as $converter) {
-      $converter->process($defaults, $route, $converters);
+    // Skip this enhancer if there are no parameter definitions.
+    if (!$parameters = $route->getOption('parameters')) {
+      return $defaults;
     }
 
-    // Check if all upcasting yielded a result.
-    // If an upcast value is NULL do a 404.
-    foreach ($converters as $variable) {
-      if ($defaults[$variable] === NULL) {
+    // Invoke the registered converter for each parameter.
+    foreach ($parameters as $name => $definition) {
+      if (!isset($defaults[$name])) {
+        // Do not try to convert anything that is already set to NULL.
+        continue;
+      }
+
+      if (!isset($definition['converter'])) {
+        // Continue if no converter has been specified.
+        continue;
+      }
+
+      // If a converter returns NULL it means that the parameter could not be
+      // converted in which case we throw a 404.
+      $defaults[$name] = $this->getConverter($definition['converter'])->convert($defaults[$name], $definition, $name, $defaults, $request);
+      if (!isset($defaults[$name])) {
         throw new NotFoundHttpException();
       }
     }
 
     return $defaults;
   }
+
 }
+
diff --git a/core/modules/system/lib/Drupal/system/Tests/ParamConverter/UpcastingTest.php b/core/modules/system/lib/Drupal/system/Tests/ParamConverter/UpcastingTest.php
index 11874555c20dc485dd6f1e6f2e3ca251e7b6661c..ad966e2a24dd1d2deaed05daaa603bdae30175c7 100644
--- a/core/modules/system/lib/Drupal/system/Tests/ParamConverter/UpcastingTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/ParamConverter/UpcastingTest.php
@@ -50,15 +50,13 @@ public function testUpcasting() {
     $this->assertRaw("user: {$user->label()}, node: {$node->label()}, foo: $foo", 'user and node upcast by entity name');
 
     // paramconverter_test/test_node_user_user/{node}/{foo}/{user}
-    // converters:
-    //   foo: 'user'
-    $this->drupalGet('paramconverter_test/test_node_user_user/' . $node->id() . '/' . $user->id() . "/" . $user->id());
+    // options.parameters.foo.type = entity:user
+    $this->drupalGet("paramconverter_test/test_node_user_user/{$node->nid}/" . $user->id() . "/" . $user->id());
     $this->assertRaw("user: {$user->label()}, node: {$node->label()}, foo: {$user->label()}", 'foo converted to user as well');
 
     // paramconverter_test/test_node_node_foo/{user}/{node}/{foo}
-    // converters:
-    //   user: 'node'
-    $this->drupalGet('paramconverter_test/test_node_node_foo/' . $node->id() . '/' . $node->id() . "/$foo");
+    // options.parameters.user.type = entity:node
+    $this->drupalGet("paramconverter_test/test_node_node_foo/{$node->nid}/{$node->nid}/$foo");
     $this->assertRaw("user: {$node->label()}, node: {$node->label()}, foo: $foo", 'user is upcast to node (rather than to user)');
   }
 
@@ -69,9 +67,8 @@ public function testSameTypes() {
     $node = $this->drupalCreateNode(array('title' => $this->randomName(8)));
     $parent = $this->drupalCreateNode(array('title' => $this->randomName(8)));
     // paramconverter_test/node/{node}/set/parent/{parent}
-    // converters:
-    //   parent: 'node'
-    $this->drupalGet("paramconverter_test/node/" . $node->id() . "/set/parent/" . $parent->id());
+    // options.parameters.parent.type = entity:node
+    $this->drupalGet("paramconverter_test/node/" . $node->nid . "/set/parent/" . $parent->nid);
     $this->assertRaw("Setting '" . $parent->title . "' as parent of '" . $node->title . "'.");
   }
 }
diff --git a/core/modules/system/tests/modules/paramconverter_test/paramconverter_test.routing.yml b/core/modules/system/tests/modules/paramconverter_test/paramconverter_test.routing.yml
index 9d226e422b7b7ab796ac3b5a6ce1261728fb8749..10efb11375363a7634698ab42613ec18a9a3be66 100644
--- a/core/modules/system/tests/modules/paramconverter_test/paramconverter_test.routing.yml
+++ b/core/modules/system/tests/modules/paramconverter_test/paramconverter_test.routing.yml
@@ -12,8 +12,9 @@ paramconverter_test_node_user_user:
   requirements:
     _access: 'TRUE'
   options:
-    converters:
-      foo: 'user'
+    parameters:
+      foo:
+        type: 'entity:user'
 
 paramconverter_test_node_node_foo:
   pattern: '/paramconverter_test/test_node_node_foo/{user}/{node}/{foo}'
@@ -22,8 +23,9 @@ paramconverter_test_node_node_foo:
   requirements:
     _access: 'TRUE'
   options:
-    converters:
-      user: 'node'
+    parameters:
+      user:
+        type: 'entity:node'
 
 paramconverter_test_node_set_parent:
   pattern: '/paramconverter_test/node/{node}/set/parent/{parent}'
@@ -32,5 +34,6 @@ paramconverter_test_node_set_parent:
   defaults:
     _content: '\Drupal\paramconverter_test\TestControllers::testNodeSetParent'
   options:
-    converters:
-      parent: 'node'
+    parameters:
+      parent:
+        type: 'entity:node'
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ParamConverter/ViewUIConverter.php b/core/modules/views_ui/lib/Drupal/views_ui/ParamConverter/ViewUIConverter.php
index f828de1f648b37de00f4116890955ff019119a2c..ba3a030f96d99893b9adba50791b2d3856a40450 100644
--- a/core/modules/views_ui/lib/Drupal/views_ui/ParamConverter/ViewUIConverter.php
+++ b/core/modules/views_ui/lib/Drupal/views_ui/ParamConverter/ViewUIConverter.php
@@ -7,16 +7,31 @@
 
 namespace Drupal\views_ui\ParamConverter;
 
+use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\ParamConverter\EntityConverter;
+use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Route;
 use Drupal\Core\ParamConverter\ParamConverterInterface;
 use Drupal\user\TempStoreFactory;
-use Drupal\views\ViewStorageInterface;
 use Drupal\views_ui\ViewUI;
 
 /**
  * Provides upcasting for a view entity to be used in the Views UI.
+ *
+ * Example:
+ *
+ * pattern: '/some/{view}/and/{bar}'
+ * options:
+ *   parameters:
+ *     view:
+ *       type: 'entity:view'
+ *       tempstore: TRUE
+ *
+ * The value for {view} will be converted to a view entity prepared for the
+ * Views UI and loaded from the views temp store, but it will not touch the
+ * value for {bar}.
  */
-class ViewUIConverter implements ParamConverterInterface {
+class ViewUIConverter extends EntityConverter implements ParamConverterInterface {
 
   /**
    * Stores the tempstore factory.
@@ -31,77 +46,49 @@ class ViewUIConverter implements ParamConverterInterface {
    * @param \Drupal\user\TempStoreFactory $temp_store_factory
    *   The factory for the temp store object.
    */
-  public function __construct(TempStoreFactory $temp_store_factory) {
+  public function __construct(EntityManager $entity_manager, TempStoreFactory $temp_store_factory) {
+    parent::__construct($entity_manager);
+
     $this->tempStoreFactory = $temp_store_factory;
   }
 
   /**
-   * Tries to upcast every view entity to a decorated ViewUI object.
-   *
-   * The key refers to the portion of the route that is a view entity that
-   * should be prepared for the Views UI. If there is a non-null value, it will
-   * be used as the collection of a temp store object used for loading.
-   *
-   * Example:
-   *
-   * pattern: '/some/{view}/and/{foo}/and/{bar}'
-   * options:
-   *   converters:
-   *     foo: 'view'
-   *   tempstore:
-   *     view: 'views'
-   *     foo: NULL
-   *
-   * The values for {view} and {foo} will be converted to view entities prepared
-   * for the Views UI, with {view} being loaded from the views temp store, but
-   * it will not touch the value for {bar}.
-   *
-   * Note: This requires that the placeholder either be named {view}, or that a
-   * converter is specified as done above for {foo}.
-   *
-   * It will still process variables which are marked as converted. It will mark
-   * any variable it processes as converted.
-   *
-   * @param array &$variables
-   *   Array of values to convert to their corresponding objects, if applicable.
-   * @param \Symfony\Component\Routing\Route $route
-   *   The route object.
-   * @param array &$converted
-   *   Array collecting the names of all variables which have been
-   *   altered by a converter.
+   * {@inheritdoc}
    */
-  public function process(array &$variables, Route $route, array &$converted) {
-    // If nothing was specified to convert, return.
-    $options = $route->getOptions();
-    if (!isset($options['tempstore'])) {
+  public function convert($value, $definition, $name, array $defaults, Request $request) {
+    if (!$entity = parent::convert($value, $definition, $name, $defaults, $request)) {
       return;
     }
 
-    foreach ($options['tempstore'] as $name => $collection) {
-      // Only convert if the variable is a view.
-      if ($variables[$name] instanceof ViewStorageInterface) {
-        // Get the temp store for this variable if it needs one.
-        // Attempt to load the view from the temp store, synchronize its
-        // status with the existing view, and store the lock metadata.
-        if ($collection && ($temp_store = $this->tempStoreFactory->get($collection)) && ($view = $temp_store->get($variables[$name]->id()))) {
-          if ($variables[$name]->status()) {
-            $view->enable();
-          }
-          else {
-            $view->disable();
-          }
-          $view->lock = $temp_store->getMetadata($variables[$name]->id());
-        }
-        // Otherwise, decorate the existing view for use in the UI.
-        else {
-          $view = new ViewUI($variables[$name]);
-        }
-
-        // Store the new view and mark this variable as converted.
-        $variables[$name] = $view;
-        $converted[] = $name;
+    // Get the temp store for this variable if it needs one. Attempt to load the
+    // view from the temp store, synchronize its status with the existing view,
+    // and store the lock metadata.
+    $store = $this->tempStoreFactory->get('views');
+    if ($view = $store->get($value)) {
+      if ($entity->status()) {
+        $view->enable();
       }
+      else {
+        $view->disable();
+      }
+      $view->lock = $store->getMetadata($value);
+    }
+    // Otherwise, decorate the existing view for use in the UI.
+    else {
+      $view = new ViewUI($entity);
+    }
+
+    return $view;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applies($definition, $name, Route $route) {
+    if (parent::applies($definition, $name, $route)) {
+      return !empty($definition['tempstore']) && $definition['type'] === 'entity:view';
     }
+    return FALSE;
   }
 
 }
diff --git a/core/modules/views_ui/views_ui.routing.yml b/core/modules/views_ui/views_ui.routing.yml
index 0d44630e1e78966abab1f08292596342e67c7fd4..cc56c8bf3963424664277bc6b6903288c47b96aa 100644
--- a/core/modules/views_ui/views_ui.routing.yml
+++ b/core/modules/views_ui/views_ui.routing.yml
@@ -72,8 +72,9 @@ views_ui.autocomplete:
 views_ui.edit:
   pattern: '/admin/structure/views/view/{view}'
   options:
-    tempstore:
-      view: 'views'
+    parameters:
+      view:
+        tempstore: TRUE
   defaults:
     _controller: '\Drupal\views_ui\Controller\ViewsUIController::edit'
   requirements:
@@ -82,8 +83,9 @@ views_ui.edit:
 views_ui.edit.display:
   pattern: '/admin/structure/views/view/{view}/edit/{display_id}'
   options:
-    tempstore:
-      view: 'views'
+    parameters:
+      view:
+        tempstore: TRUE
   defaults:
     _controller: '\Drupal\views_ui\Controller\ViewsUIController::edit'
     display_id: NULL
@@ -93,8 +95,9 @@ views_ui.edit.display:
 views_ui.preview:
   pattern: '/admin/structure/views/view/{view}/preview/{display_id}'
   options:
-    tempstore:
-      view: 'views'
+    parameters:
+      view:
+        tempstore: TRUE
   defaults:
     _entity_form: 'view.preview'
     display_id: NULL
@@ -111,8 +114,9 @@ views_ui.breakLock:
 views_ui.form.addItem:
   pattern: '/admin/structure/views/{js}/add-item/{view}/{display_id}/{type}'
   options:
-    tempstore:
-      view: 'views'
+    parameters:
+      view:
+        tempstore: TRUE
   defaults:
     _controller: '\Drupal\views_ui\Form\Ajax\AddItem::getForm'
   requirements:
@@ -122,8 +126,9 @@ views_ui.form.addItem:
 views_ui.form.editDetails:
   pattern: '/admin/structure/views/{js}/edit-details/{view}/{display_id}'
   options:
-    tempstore:
-      view: 'views'
+    parameters:
+      view:
+        tempstore: TRUE
   defaults:
     _controller: '\Drupal\views_ui\Form\Ajax\EditDetails::getForm'
   requirements:
@@ -133,8 +138,9 @@ views_ui.form.editDetails:
 views_ui.form.reorderDisplays:
   pattern: '/admin/structure/views/{js}/reorder-displays/{view}/{display_id}'
   options:
-    tempstore:
-      view: 'views'
+    parameters:
+      view:
+        tempstore: TRUE
   defaults:
     _controller: '\Drupal\views_ui\Form\Ajax\ReorderDisplays::getForm'
   requirements:
@@ -144,8 +150,9 @@ views_ui.form.reorderDisplays:
 views_ui.form.analyze:
   pattern: '/admin/structure/views/{js}/analyze/{view}/{display_id}'
   options:
-    tempstore:
-      view: 'views'
+    parameters:
+      view:
+        tempstore: TRUE
   defaults:
     _controller: '\Drupal\views_ui\Form\Ajax\Analyze::getForm'
   requirements:
@@ -155,8 +162,9 @@ views_ui.form.analyze:
 views_ui.form.rearrange:
   pattern: '/admin/structure/views/{js}/rearrange/{view}/{display_id}/{type}'
   options:
-    tempstore:
-      view: 'views'
+    parameters:
+      view:
+        tempstore: TRUE
   defaults:
     _controller: '\Drupal\views_ui\Form\Ajax\Rearrange::getForm'
   requirements:
@@ -166,8 +174,9 @@ views_ui.form.rearrange:
 views_ui.form.rearrangeFilter:
   pattern: '/admin/structure/views/{js}/rearrange-filter/{view}/{display_id}'
   options:
-    tempstore:
-      view: 'views'
+    parameters:
+      view:
+        tempstore: TRUE
   defaults:
     _controller: '\Drupal\views_ui\Form\Ajax\RearrangeFilter::getForm'
   requirements:
@@ -177,8 +186,9 @@ views_ui.form.rearrangeFilter:
 views_ui.form.display:
   pattern: '/admin/structure/views/{js}/display/{view}/{display_id}/{type}'
   options:
-    tempstore:
-      view: 'views'
+    parameters:
+      view:
+        tempstore: TRUE
   defaults:
     _controller: '\Drupal\views_ui\Form\Ajax\Display::getForm'
   requirements:
@@ -188,8 +198,9 @@ views_ui.form.display:
 views_ui.form.configItem:
   pattern: '/admin/structure/views/{js}/config-item/{view}/{display_id}/{type}/{id}'
   options:
-    tempstore:
-      view: 'views'
+    parameters:
+      view:
+        tempstore: TRUE
   defaults:
     _controller: '\Drupal\views_ui\Form\Ajax\ConfigItem::getForm'
   requirements:
@@ -199,8 +210,9 @@ views_ui.form.configItem:
 views_ui.form.configItemExtra:
   pattern: '/admin/structure/views/{js}/config-item-extra/{view}/{display_id}/{type}/{id}'
   options:
-    tempstore:
-      view: 'views'
+    parameters:
+      view:
+        tempstore: TRUE
   defaults:
     _controller: '\Drupal\views_ui\Form\Ajax\ConfigItemExtra::getForm'
   requirements:
@@ -210,8 +222,9 @@ views_ui.form.configItemExtra:
 views_ui.form.configItemGroup:
   pattern: '/admin/structure/views/{js}/config-item-group/{view}/{display_id}/{type}/{id}'
   options:
-    tempstore:
-      view: 'views'
+    parameters:
+      view:
+        tempstore: TRUE
   defaults:
     _controller: '\Drupal\views_ui\Form\Ajax\ConfigItemGroup::getForm'
     form_state: NULL
diff --git a/core/modules/views_ui/views_ui.services.yml b/core/modules/views_ui/views_ui.services.yml
index 34b274005ea6f9defd662bbce4c573f0c5f1186e..3120b87b6d4b9f65815bd94df4e73c34d0882476 100644
--- a/core/modules/views_ui/views_ui.services.yml
+++ b/core/modules/views_ui/views_ui.services.yml
@@ -1,6 +1,6 @@
 services:
   paramconverter.views_ui:
     class: Drupal\views_ui\ParamConverter\ViewUIConverter
-    arguments: ['@user.tempstore']
+    arguments: ['@plugin.manager.entity', '@user.tempstore']
     tags:
-      - { name: paramconverter }
+      - { name: paramconverter, priority: 10 }
diff --git a/core/tests/Drupal/Tests/Core/ParamConverter/ParamConverterManagerTest.php b/core/tests/Drupal/Tests/Core/ParamConverter/ParamConverterManagerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6e9a018b82a67dbdccdef66e3a01cd83aee03d53
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/ParamConverter/ParamConverterManagerTest.php
@@ -0,0 +1,145 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Tests\Core\ParamConverter\ParamConverterManagerTest.
+ */
+
+namespace Drupal\Tests\Core\ParamConverter;
+
+use Drupal\Core\ParamConverter\ParamConverterManager;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/**
+ * Tests the typed data resolver manager.
+ */
+class ParamConverterManagerTest extends UnitTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Parameter converter manager',
+      'description' => 'Tests the parameter converter manager.',
+      'group' => 'Routing',
+    );
+  }
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->container = new ContainerBuilder();
+    $this->manager = new ParamConverterManager();
+    $this->manager->setContainer($this->container);
+  }
+
+  /**
+   * Tests \Drupal\Core\ParamConverter\ParamConverterManager::addConverter().
+   *
+   * @dataProvider providerTestAddConverter
+   *
+   * @see ParamConverterManagerTest::providerTestAddConverter().
+   */
+  public function testAddConverter($unsorted, $sorted) {
+    foreach ($unsorted as $data) {
+      $this->manager->addConverter($data['name'], $data['priority']);
+    }
+
+    // Test that ResolverManager::getTypedDataResolvers() returns the resolvers
+    // in the expected order.
+    foreach ($this->manager->getConverterIds() as $key => $converter) {
+      $this->assertEquals($sorted[$key], $converter);
+    }
+  }
+
+  /**
+   * Tests \Drupal\Core\ParamConverter\ParamConverterManager::getConverter().
+   *
+   * @dataProvider providerTestGetConverter
+   *
+   * @see ParamConverterManagerTest::providerTestGetConverter().
+   */
+  public function testGetConverter($name, $priority, $class) {
+    $converter = $this->getMockBuilder('Drupal\Core\ParamConverter\ParamConverterInterface')
+      ->setMockClassName($class)
+      ->getMock();
+
+    $this->manager->addConverter($name, $priority);
+    $this->container->set($name, $converter);
+
+    $this->assertInstanceOf($class, $this->manager->getConverter($name));
+  }
+
+  /**
+   * Tests \Drupal\Core\ParamConverter\ParamConverterManager::getConverter().
+   *
+   * @expectedException InvalidArgumentException
+   */
+  public function testGetConverterException() {
+    $this->manager->getConverter('undefined.converter');
+  }
+
+  /**
+   * Provide data for parameter converter manager tests.
+   *
+   * @return array
+   *   An array of arrays, each containing the input parameters for
+   *   providerTestResolvers::testAddConverter().
+   *
+   * @see ParamConverterManagerTest::testAddConverter().
+   */
+  public function providerTestAddConverter() {
+    $converters[0]['unsorted'] = array(
+      array('name' => 'raspberry', 'priority' => 10),
+      array('name' => 'pear', 'priority' => 5),
+      array('name' => 'strawberry', 'priority' => 20),
+      array('name' => 'pineapple', 'priority' => 0),
+      array('name' => 'banana', 'priority' => -10),
+      array('name' => 'apple', 'priority' => -10),
+      array('name' => 'peach', 'priority' => 5),
+    );
+
+    $converters[0]['sorted'] = array(
+      'strawberry', 'raspberry', 'pear', 'peach',
+      'pineapple', 'banana', 'apple'
+    );
+
+    $converters[1]['unsorted'] = array(
+      array('name' => 'ape', 'priority' => 0),
+      array('name' => 'cat', 'priority' => -5),
+      array('name' => 'puppy', 'priority' => -10),
+      array('name' => 'llama', 'priority' => -15),
+      array('name' => 'giraffe', 'priority' => 10),
+      array('name' => 'zebra', 'priority' => 10),
+      array('name' => 'eagle', 'priority' => 5),
+    );
+
+    $converters[1]['sorted'] = array(
+      'giraffe', 'zebra', 'eagle', 'ape',
+      'cat', 'puppy', 'llama'
+    );
+
+    return $converters;
+  }
+
+  /**
+   * Provide data for parameter converter manager tests.
+   *
+   * @return array
+   *   An array of arrays, each containing the input parameters for
+   *   providerTestResolvers::testGetConverter().
+   *
+   * @see ParamConverterManagerTest::testGetConverter().
+   */
+  public function providerTestGetConverter() {
+    return array(
+      array('ape', 0, 'ApeConverterClass'),
+      array('cat', -5, 'CatConverterClass'),
+      array('puppy', -10, 'PuppyConverterClass'),
+      array('llama', -15, 'LlamaConverterClass'),
+      array('giraffe', 10, 'GiraffeConverterClass'),
+      array('zebra', 10, 'ZebraConverterClass'),
+      array('eagle', 5, 'EagleConverterClass'),
+    );
+  }
+
+}