diff --git a/core/core.services.yml b/core/core.services.yml
index c814824..3b3443e 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -342,8 +342,6 @@ services:
     class: Drupal\Core\ParamConverter\ParamConverterManager
     calls:
       - [setContainer, ['@service_container']]
-    tags:
-      - { name: route_enhancer }
   paramconverter_subscriber:
     class: Drupal\Core\EventSubscriber\ParamConverterSubscriber
     tags:
@@ -373,6 +371,12 @@ services:
     class: Drupal\Core\EventSubscriber\AjaxResponseSubscriber
     tags:
       - { name: event_subscriber }
+  route_enhancer.param_conversion:
+    class: Drupal\Core\Routing\Enhancer\ParamConversionEnhancer
+    arguments: ['@paramconverter_manager']
+    tags:
+      - { name: route_enhancer }
+      - { name: event_subscriber }
   route_enhancer.authentication:
     class: Drupal\Core\Routing\Enhancer\AuthenticationEnhancer
     calls:
diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index bbf5c43..96f810e 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -9,13 +9,13 @@
 use Drupal\Component\Utility\String;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Language\Language;
+use Drupal\Core\ParamConverter\ParamNotConvertedException;
 use Drupal\Core\Routing\RequestHelper;
 use Drupal\Core\Template\Attribute;
 use Drupal\menu_link\Entity\MenuLink;
 use Drupal\menu_link\MenuLinkStorageController;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Symfony\Component\Routing\Route;
 
 /**
@@ -934,7 +934,7 @@ function menu_item_route_access(Route $route, $href, &$map, Request $request = N
   try {
     $request->attributes->add(\Drupal::service('router')->matchRequest($request));
   }
-  catch (NotFoundHttpException $e) {
+  catch (ParamNotConvertedException $e) {
     return FALSE;
   }
 
diff --git a/core/lib/Drupal/Core/Access/AccessManager.php b/core/lib/Drupal/Core/Access/AccessManager.php
index 3bb2194..42fc39a 100644
--- a/core/lib/Drupal/Core/Access/AccessManager.php
+++ b/core/lib/Drupal/Core/Access/AccessManager.php
@@ -7,7 +7,8 @@
 
 namespace Drupal\Core\Access;
 
-use Drupal\Core\ParamConverter\ParamConverterManager;
+use Drupal\Core\ParamConverter\ParamConverterManagerInterface;
+use Drupal\Core\ParamConverter\ParamNotConvertedException;
 use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
 use Drupal\Core\Routing\RequestHelper;
 use Drupal\Core\Routing\RouteProviderInterface;
@@ -17,7 +18,6 @@
 use Symfony\Component\Routing\Route;
 use Symfony\Component\DependencyInjection\ContainerAware;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Symfony\Component\Routing\Exception\RouteNotFoundException;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 
@@ -73,7 +73,7 @@ class AccessManager extends ContainerAware {
   /**
    * The paramconverter manager.
    *
-   * @var \Drupal\Core\ParamConverter\ParamConverterManager
+   * @var \Drupal\Core\ParamConverter\ParamConverterManagerInterface
    */
   protected $paramConverterManager;
 
@@ -91,10 +91,10 @@ class AccessManager extends ContainerAware {
    *   The route provider.
    * @param \Symfony\Component\Routing\Generator\UrlGeneratorInterface $url_generator
    *   The url generator.
-   * @param \Drupal\Core\ParamConverter\ParamConverterManager $paramconverter_manager
+   * @param \Drupal\Core\ParamConverter\ParamConverterManagerInterface $paramconverter_manager
    *   The param converter manager.
    */
-  public function __construct(RouteProviderInterface $route_provider, UrlGeneratorInterface $url_generator, ParamConverterManager $paramconverter_manager) {
+  public function __construct(RouteProviderInterface $route_provider, UrlGeneratorInterface $url_generator, ParamConverterManagerInterface $paramconverter_manager) {
     $this->routeProvider = $route_provider;
     $this->urlGenerator = $url_generator;
     $this->paramConverterManager = $paramconverter_manager;
@@ -202,14 +202,14 @@ public function checkNamedRoute($route_name, array $parameters = array(), Accoun
         $defaults = $parameters + $route->getDefaults();
         $route_request = RequestHelper::duplicate($this->request, $this->urlGenerator->generate($route_name, $defaults));
         $defaults[RouteObjectInterface::ROUTE_OBJECT] = $route;
-        $route_request->attributes->add($this->paramConverterManager->enhance($defaults, $route_request));
+        $route_request->attributes->add($this->paramConverterManager->convert($defaults, $route_request));
       }
       return $this->check($route, $route_request, $account);
     }
     catch (RouteNotFoundException $e) {
       return FALSE;
     }
-    catch (NotFoundHttpException $e) {
+    catch (ParamNotConvertedException $e) {
       return FALSE;
     }
   }
diff --git a/core/lib/Drupal/Core/EventSubscriber/ParamConverterSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ParamConverterSubscriber.php
index 7d385f5..b89e9ff 100644
--- a/core/lib/Drupal/Core/EventSubscriber/ParamConverterSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/ParamConverterSubscriber.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\EventSubscriber;
 
-use Drupal\Core\ParamConverter\ParamConverterManager;
+use Drupal\Core\ParamConverter\ParamConverterManagerInterface;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 use Drupal\Core\Routing\RoutingEvents;
 use Drupal\Core\Routing\RouteBuildEvent;
@@ -20,18 +20,18 @@ class ParamConverterSubscriber implements EventSubscriberInterface {
   /**
    * The parameter converter manager.
    *
-   * @var \Drupal\Core\ParamConverter\ParamConverterManager
+   * @var \Drupal\Core\ParamConverter\ParamConverterManagerInterface
    */
   protected $paramConverterManager;
 
   /**
    * Constructs a new ParamConverterSubscriber.
    *
-   * @param \Drupal\Core\ParamConverter\ParamConverterManager $param_converter_manager
+   * @param \Drupal\Core\ParamConverter\ParamConverterManagerInterface $param_converter_manager
    *   The parameter converter manager that will be responsible for upcasting
    *   request attributes.
    */
-  public function __construct(ParamConverterManager $param_converter_manager) {
+  public function __construct(ParamConverterManagerInterface $param_converter_manager) {
     $this->paramConverterManager = $param_converter_manager;
   }
 
diff --git a/core/lib/Drupal/Core/ParamConverter/ParamConverterManager.php b/core/lib/Drupal/Core/ParamConverter/ParamConverterManager.php
index cad587b..48f057b 100644
--- a/core/lib/Drupal/Core/ParamConverter/ParamConverterManager.php
+++ b/core/lib/Drupal/Core/ParamConverter/ParamConverterManager.php
@@ -8,10 +8,7 @@
 namespace Drupal\Core\ParamConverter;
 
 use Symfony\Component\DependencyInjection\ContainerAware;
-use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
-use Symfony\Component\HttpFoundation\ParameterBag;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Symfony\Component\Routing\RouteCollection;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -21,7 +18,7 @@
  * A typical use case for this would be upcasting (converting) a node id to a
  * node entity.
  */
-class ParamConverterManager extends ContainerAware implements RouteEnhancerInterface {
+class ParamConverterManager extends ContainerAware implements ParamConverterManagerInterface {
 
   /**
    * An array of registered converter service ids.
@@ -45,15 +42,7 @@ class ParamConverterManager extends ContainerAware implements RouteEnhancerInter
   protected $converters = array();
 
   /**
-   * Registers a parameter converter with the manager.
-   *
-   * @param string $converter
-   *   The parameter converter service id to register.
-   * @param int $priority
-   *   (optional) The priority of the converter. Defaults to 0.
-   *
-   * @return \Drupal\Core\ParamConverter\ParamConverterManager
-   *   The called object for chaining.
+   * {@inheritdoc}
    */
   public function addConverter($converter, $priority = 0) {
     if (empty($this->converterIds[$priority])) {
@@ -65,10 +54,7 @@ public function addConverter($converter, $priority = 0) {
   }
 
   /**
-   * Sorts the converter service ids and flattens them.
-   *
-   * @return array
-   *   The sorted parameter converter service ids.
+   * {@inheritdoc}
    */
   public function getConverterIds() {
     if (!isset($this->sortedConverterIds)) {
@@ -82,16 +68,7 @@ public function getConverterIds() {
   }
 
   /**
-   * 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.
+   * {@inheritdoc}
    */
   public function getConverter($converter) {
     if (isset($this->converters[$converter])) {
@@ -104,10 +81,7 @@ public function getConverter($converter) {
   }
 
   /**
-   * Saves a list of applicable converters to each route.
-   *
-   * @param \Symfony\Component\Routing\RouteCollection $routes
-   *   A collection of routes to apply converters to.
+   * {@inheritdoc}
    */
   public function setRouteParameterConverters(RouteCollection $routes) {
     foreach ($routes->all() as $route) {
@@ -137,33 +111,11 @@ public function setRouteParameterConverters(RouteCollection $routes) {
   }
 
   /**
-   * Invokes the registered converter for each defined parameter on a route.
-   *
-   * @param array $defaults
-   *   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.
+   * {@inheritdoc}
    */
-  public function enhance(array $defaults, Request $request) {
-    // Store a backup of the raw $defaults values corresponding to
-    // variables in the route path pattern.
+  public function convert(array $defaults, Request $request) {
+    /** @var $route \Symfony\Component\Routing\Route */
     $route = $defaults[RouteObjectInterface::ROUTE_OBJECT];
-    $variables = array_flip($route->compile()->getVariables());
-    // Foreach  will copy the values from the array it iterates. Even if they
-    // are references, use it to break them. This avoids any scenarios where raw
-    // variables also get replaced with converted values.
-    $raw_variables = array();
-    foreach (array_intersect_key($defaults, $variables) as $key => $value) {
-      $raw_variables[$key] = $value;
-    }
-    $defaults['_raw_variables'] = new ParameterBag($raw_variables);
 
     // Skip this enhancer if there are no parameter definitions.
     if (!$parameters = $route->getOption('parameters')) {
@@ -183,10 +135,10 @@ public function enhance(array $defaults, Request $request) {
       }
 
       // If a converter returns NULL it means that the parameter could not be
-      // converted in which case we throw a 404.
+      // converted.
       $defaults[$name] = $this->getConverter($definition['converter'])->convert($defaults[$name], $definition, $name, $defaults, $request);
       if (!isset($defaults[$name])) {
-        throw new NotFoundHttpException();
+        throw new ParamNotConvertedException(sprintf('The "%s" parameter was not converted for the path "%s" (route name: "%s")', $name, $route->getPath(), $defaults[RouteObjectInterface::ROUTE_NAME]));
       }
     }
 
diff --git a/core/lib/Drupal/Core/ParamConverter/ParamConverterManagerInterface.php b/core/lib/Drupal/Core/ParamConverter/ParamConverterManagerInterface.php
new file mode 100644
index 0000000..8240e62
--- /dev/null
+++ b/core/lib/Drupal/Core/ParamConverter/ParamConverterManagerInterface.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\ParamConverter\ParamConverterManagerInterface.
+ */
+
+namespace Drupal\Core\ParamConverter;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Provides an interface for a parameter converter manager.
+ */
+interface ParamConverterManagerInterface {
+
+  /**
+   * Registers a parameter converter with the manager.
+   *
+   * @param string $converter
+   *   The parameter converter service id to register.
+   * @param int $priority
+   *   (optional) The priority of the converter. Defaults to 0.
+   *
+   * @return $this
+   */
+  public function addConverter($converter, $priority = 0);
+
+  /**
+   * Sorts the converter service ids and flattens them.
+   *
+   * @return array
+   *   The sorted parameter converter service ids.
+   */
+    public function getConverterIds();
+
+  /**
+   * 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);
+
+  /**
+   * Saves a list of applicable converters to each route.
+   *
+   * @param \Symfony\Component\Routing\RouteCollection $routes
+   *   A collection of routes to apply converters to.
+   */
+  public function setRouteParameterConverters(RouteCollection $routes);
+
+  /**
+   * Invokes the registered converter for each defined parameter on a route.
+   *
+   * @param array $defaults
+   *   The route defaults array.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request.
+   *
+   * @throws \Drupal\Core\ParamConverter\ParamNotConvertedException
+   *   If one of the assigned converters returned NULL because the given
+   *   variable could not be converted.
+   *
+   * @return array
+   *   The modified defaults.
+   */
+  public function convert(array $defaults, Request $request);
+
+}
diff --git a/core/lib/Drupal/Core/ParamConverter/ParamNotConvertedException.php b/core/lib/Drupal/Core/ParamConverter/ParamNotConvertedException.php
new file mode 100644
index 0000000..11ed70f
--- /dev/null
+++ b/core/lib/Drupal/Core/ParamConverter/ParamNotConvertedException.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\ParamConverter\ParamNotConvertedException.
+ */
+
+namespace Drupal\Core\ParamConverter;
+
+/**
+ * Provides an exception class for a request parameter that was not converted.
+ */
+class ParamNotConvertedException extends \Exception {
+
+}
diff --git a/core/lib/Drupal/Core/Routing/Enhancer/ParamConversionEnhancer.php b/core/lib/Drupal/Core/Routing/Enhancer/ParamConversionEnhancer.php
new file mode 100644
index 0000000..614ac96
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/Enhancer/ParamConversionEnhancer.php
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Routing\Enhancer\ParamConversionEnhancer.
+ */
+
+namespace Drupal\Core\Routing\Enhancer;
+
+use Drupal\Core\ParamConverter\ParamConverterManagerInterface;
+use Drupal\Core\ParamConverter\ParamNotConvertedException;
+use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpFoundation\ParameterBag;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\HttpKernel\KernelEvents;
+
+/**
+ * Provides a route enhancer that handles parameter conversion.
+ */
+class ParamConversionEnhancer implements RouteEnhancerInterface, EventSubscriberInterface {
+
+  /**
+   * The parameter conversion manager.
+   *
+   * @var \Drupal\Core\ParamConverter\ParamConverterManagerInterface
+   */
+  protected $paramConverterManager;
+
+  /**
+   * Constructs a new ParamConversionEnhancer.
+   *
+   * @param \Drupal\Core\ParamConverter\ParamConverterManagerInterface $param_converter_manager
+   *   The parameter conversion manager.
+   */
+  public function __construct(ParamConverterManagerInterface $param_converter_manager) {
+    $this->paramConverterManager = $param_converter_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function enhance(array $defaults, Request $request) {
+    $defaults['_raw_variables'] = $this->copyRawVariables($defaults);
+    return $this->paramConverterManager->convert($defaults, $request);
+  }
+
+  /**
+   * Store a backup of the raw values that corresponding to the route pattern.
+   *
+   * @param array $defaults
+   *   The route defaults array.
+   *
+   * @return \Symfony\Component\HttpFoundation\ParameterBag
+   */
+  protected function copyRawVariables(array $defaults) {
+    /** @var $route \Symfony\Component\Routing\Route */
+    $route = $defaults[RouteObjectInterface::ROUTE_OBJECT];
+    $variables = array_flip($route->compile()->getVariables());
+    // Foreach will copy the values from the array it iterates. Even if they
+    // are references, use it to break them. This avoids any scenarios where raw
+    // variables also get replaced with converted values.
+    $raw_variables = array();
+    foreach (array_intersect_key($defaults, $variables) as $key => $value) {
+      $raw_variables[$key] = $value;
+    }
+    return new ParameterBag($raw_variables);
+  }
+
+  /**
+   * Catches failed parameter conversions and throw a 404 instead.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
+   */
+  public function onException(GetResponseForExceptionEvent $event) {
+    $exception = $event->getException();
+    if ($exception instanceof ParamNotConvertedException) {
+      $event->setException(new NotFoundHttpException($exception->getMessage(), $exception));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[KernelEvents::EXCEPTION][] = array('onException', 0);
+    return $events;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Routing/UrlMatcher.php b/core/lib/Drupal/Core/Routing/UrlMatcher.php
index 9e973d4..9879d28 100644
--- a/core/lib/Drupal/Core/Routing/UrlMatcher.php
+++ b/core/lib/Drupal/Core/Routing/UrlMatcher.php
@@ -30,7 +30,16 @@ public function finalMatch(RouteCollection $collection, Request $request) {
     $context = new RequestContext();
     $context->fromRequest($request);
     $this->setContext($context);
-    return $this->match('/' . $request->attributes->get('_system_path'));
+    if ($request->attributes->has('_system_path')) {
+      // _system_path never has leading or trailing slashes.
+      $path = '/' . $request->attributes->get('_system_path');
+    }
+    else {
+      // getPathInfo() always has leading slash, and might or might not have a
+      // trailing slash.
+      $path = rtrim($request->getPathInfo(), '/');
+    }
+    return $this->match($path);
   }
 
   /**
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationController.php b/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationController.php
index 55e7318..0235678 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationController.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationController.php
@@ -12,12 +12,12 @@
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\ParamConverter\ParamNotConvertedException;
 use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Symfony\Component\Routing\Exception\ResourceNotFoundException;
 use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
 
@@ -251,7 +251,7 @@ protected function getRequestForPath(Request $request, $path) {
       $route_request->attributes->add($this->router->matchRequest($route_request));
       return $route_request;
     }
-    catch (NotFoundHttpException $e) {
+    catch (ParamNotConvertedException $e) {
       return NULL;
     }
     catch (ResourceNotFoundException $e) {
diff --git a/core/modules/edit/lib/Drupal/edit/Access/EditEntityAccessCheck.php b/core/modules/edit/lib/Drupal/edit/Access/EditEntityAccessCheck.php
index 827c06f..3c6dbcd 100644
--- a/core/modules/edit/lib/Drupal/edit/Access/EditEntityAccessCheck.php
+++ b/core/modules/edit/lib/Drupal/edit/Access/EditEntityAccessCheck.php
@@ -44,7 +44,9 @@ public function access(Route $route, Request $request, AccountInterface $account
     // @todo Request argument validation and object loading should happen
     //   elsewhere in the request processing pipeline:
     //   http://drupal.org/node/1798214.
-    $this->validateAndUpcastRequestAttributes($request);
+    if (!$this->validateAndUpcastRequestAttributes($request)) {
+      return static::KILL;
+    }
 
     return $this->accessEditEntity($request->attributes->get('entity'), $account)  ? static::ALLOW : static::DENY;
   }
@@ -65,14 +67,16 @@ protected function validateAndUpcastRequestAttributes(Request $request) {
       $entity_id = $entity;
       $entity_type = $request->attributes->get('entity_type');
       if (!$entity_type || !$this->entityManager->getDefinition($entity_type)) {
-        throw new NotFoundHttpException();
+        return FALSE;
       }
       $entity = $this->entityManager->getStorageController($entity_type)->load($entity_id);
       if (!$entity) {
-        throw new NotFoundHttpException();
+        return FALSE;
       }
       $request->attributes->set('entity', $entity);
     }
+
+    return TRUE;
   }
 
 }
diff --git a/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php b/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php
index 9de7347..50aaf64 100644
--- a/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php
+++ b/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php
@@ -12,7 +12,6 @@
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Drupal\Core\Entity\EntityInterface;
 
 /**
@@ -44,7 +43,9 @@ public function access(Route $route, Request $request, AccountInterface $account
     // @todo Request argument validation and object loading should happen
     //   elsewhere in the request processing pipeline:
     //   http://drupal.org/node/1798214.
-    $this->validateAndUpcastRequestAttributes($request);
+    if (!$this->validateAndUpcastRequestAttributes($request)) {
+      return static::KILL;
+    }
 
     return $this->accessEditEntityField($request->attributes->get('entity'), $request->attributes->get('field_name'))  ? static::ALLOW : static::DENY;
   }
@@ -65,11 +66,11 @@ protected function validateAndUpcastRequestAttributes(Request $request) {
       $entity_id = $entity;
       $entity_type = $request->attributes->get('entity_type');
       if (!$entity_type || !$this->entityManager->getDefinition($entity_type)) {
-        throw new NotFoundHttpException();
+        return FALSE;
       }
       $entity = $this->entityManager->getStorageController($entity_type)->load($entity_id);
       if (!$entity) {
-        throw new NotFoundHttpException();
+        return FALSE;
       }
       $request->attributes->set('entity', $entity);
     }
@@ -77,12 +78,14 @@ protected function validateAndUpcastRequestAttributes(Request $request) {
     // Validate the field name and language.
     $field_name = $request->attributes->get('field_name');
     if (!$field_name || !$entity->hasField($field_name)) {
-      throw new NotFoundHttpException();
+      return FALSE;
     }
     $langcode = $request->attributes->get('langcode');
     if (!$langcode || !$entity->hasTranslation($langcode)) {
-      throw new NotFoundHttpException();
+      return FALSE;
     }
+
+    return TRUE;
   }
 
 }
diff --git a/core/modules/edit/tests/Drupal/edit/Tests/Access/EditEntityAccessCheckTest.php b/core/modules/edit/tests/Drupal/edit/Tests/Access/EditEntityAccessCheckTest.php
index 0d5ca57..9e8d050 100644
--- a/core/modules/edit/tests/Drupal/edit/Tests/Access/EditEntityAccessCheckTest.php
+++ b/core/modules/edit/tests/Drupal/edit/Tests/Access/EditEntityAccessCheckTest.php
@@ -117,8 +117,6 @@ public function testAccess(EntityInterface $entity, $expected_result) {
 
   /**
    * Tests the access method with an undefined entity type.
-   *
-   * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
    */
   public function testAccessWithUndefinedEntityType() {
     $route = new Route('/edit/form/test_entity/1/body/und/full', array(), array('_access_edit_entity' => 'TRUE'));
@@ -131,13 +129,11 @@ public function testAccessWithUndefinedEntityType() {
       ->will($this->returnValue(NULL));
 
     $account = $this->getMock('Drupal\Core\Session\AccountInterface');
-    $this->editAccessCheck->access($route, $request, $account);
+    $this->assertSame(AccessCheckInterface::KILL, $this->editAccessCheck->access($route, $request, $account));
   }
 
   /**
    * Tests the access method with a non existing entity.
-   *
-   * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
    */
   public function testAccessWithNotExistingEntity() {
     $route = new Route('/edit/form/test_entity/1/body/und/full', array(), array('_access_edit_entity_field' => 'TRUE'));
@@ -156,7 +152,7 @@ public function testAccessWithNotExistingEntity() {
       ->will($this->returnValue(NULL));
 
     $account = $this->getMock('Drupal\Core\Session\AccountInterface');
-    $this->editAccessCheck->access($route, $request, $account);
+    $this->assertSame(AccessCheckInterface::KILL, $this->editAccessCheck->access($route, $request, $account));
   }
 
 }
diff --git a/core/modules/edit/tests/Drupal/edit/Tests/Access/EditEntityFieldAccessCheckTest.php b/core/modules/edit/tests/Drupal/edit/Tests/Access/EditEntityFieldAccessCheckTest.php
index b9c847d..0791249 100644
--- a/core/modules/edit/tests/Drupal/edit/Tests/Access/EditEntityFieldAccessCheckTest.php
+++ b/core/modules/edit/tests/Drupal/edit/Tests/Access/EditEntityFieldAccessCheckTest.php
@@ -144,8 +144,6 @@ public function testAccess(EntityInterface $entity, FieldInterface $field = NULL
 
   /**
    * Tests the access method with an undefined entity type.
-   *
-   * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
    */
   public function testAccessWithUndefinedEntityType() {
     $route = new Route('/edit/form/test_entity/1/body/und/full', array(), array('_access_edit_entity_field' => 'TRUE'));
@@ -158,13 +156,11 @@ public function testAccessWithUndefinedEntityType() {
       ->will($this->returnValue(NULL));
 
     $account = $this->getMock('Drupal\Core\Session\AccountInterface');
-    $this->editAccessCheck->access($route, $request, $account);
+    $this->assertSame(AccessCheckInterface::KILL, $this->editAccessCheck->access($route, $request, $account));
   }
 
   /**
    * Tests the access method with a non existing entity.
-   *
-   * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
    */
   public function testAccessWithNotExistingEntity() {
     $route = new Route('/edit/form/test_entity/1/body/und/full', array(), array('_access_edit_entity_field' => 'TRUE'));
@@ -183,13 +179,11 @@ public function testAccessWithNotExistingEntity() {
       ->will($this->returnValue(NULL));
 
     $account = $this->getMock('Drupal\Core\Session\AccountInterface');
-    $this->editAccessCheck->access($route, $request, $account);
+    $this->assertSame(AccessCheckInterface::KILL, $this->editAccessCheck->access($route, $request, $account));
   }
 
   /**
    * Tests the access method with a forgotten passed field_name.
-   *
-   * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
    */
   public function testAccessWithNotPassedFieldName() {
     $route = new Route('/edit/form/test_entity/1/body/und/full', array(), array('_access_edit_entity_field' => 'TRUE'));
@@ -198,13 +192,11 @@ public function testAccessWithNotPassedFieldName() {
     $request->attributes->set('entity', $this->createMockEntity());
 
     $account = $this->getMock('Drupal\Core\Session\AccountInterface');
-    $this->editAccessCheck->access($route, $request, $account);
+    $this->assertSame(AccessCheckInterface::KILL, $this->editAccessCheck->access($route, $request, $account));
   }
 
   /**
    * Tests the access method with a non existing field.
-   *
-   * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
    */
   public function testAccessWithNonExistingField() {
     $route = new Route('/edit/form/test_entity/1/body/und/full', array(), array('_access_edit_entity_field' => 'TRUE'));
@@ -214,13 +206,11 @@ public function testAccessWithNonExistingField() {
     $request->attributes->set('field_name', 'not_valid');
 
     $account = $this->getMock('Drupal\Core\Session\AccountInterface');
-    $this->editAccessCheck->access($route, $request, $account);
+    $this->assertSame(AccessCheckInterface::KILL, $this->editAccessCheck->access($route, $request, $account));
   }
 
   /**
    * Tests the access method with a forgotten passed language.
-   *
-   * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
    */
   public function testAccessWithNotPassedLanguage() {
     $route = new Route('/edit/form/test_entity/1/body/und/full', array(), array('_access_edit_entity_field' => 'TRUE'));
@@ -230,13 +220,11 @@ public function testAccessWithNotPassedLanguage() {
     $request->attributes->set('field_name', 'valid');
 
     $account = $this->getMock('Drupal\Core\Session\AccountInterface');
-    $this->editAccessCheck->access($route, $request, $account);
+    $this->assertSame(AccessCheckInterface::KILL, $this->editAccessCheck->access($route, $request, $account));
   }
 
   /**
    * Tests the access method with an invalid language.
-   *
-   * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
    */
   public function testAccessWithInvalidLanguage() {
     $entity = $this->createMockEntity();
@@ -253,7 +241,7 @@ public function testAccessWithInvalidLanguage() {
     $request->attributes->set('langcode', 'xx-lolspeak');
 
     $account = $this->getMock('Drupal\Core\Session\AccountInterface');
-    $this->editAccessCheck->access($route, $request, $account);
+    $this->assertSame(AccessCheckInterface::KILL, $this->editAccessCheck->access($route, $request, $account));
   }
 
   /**
diff --git a/core/modules/system/lib/Drupal/system/PathBasedBreadcrumbBuilder.php b/core/modules/system/lib/Drupal/system/PathBasedBreadcrumbBuilder.php
index 4ab853c..f39a1a5 100644
--- a/core/modules/system/lib/Drupal/system/PathBasedBreadcrumbBuilder.php
+++ b/core/modules/system/lib/Drupal/system/PathBasedBreadcrumbBuilder.php
@@ -12,10 +12,10 @@
 use Drupal\Core\Controller\TitleResolverInterface;
 use Drupal\Core\Access\AccessManager;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\ParamConverter\ParamNotConvertedException;
 use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
 use Drupal\Component\Utility\Unicode;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
 use Symfony\Component\Routing\Exception\ResourceNotFoundException;
@@ -192,7 +192,7 @@ protected function getRequestForPath($path, array $exclude) {
       $request->attributes->add($this->router->matchRequest($request));
       return $request;
     }
-    catch (NotFoundHttpException $e) {
+    catch (ParamNotConvertedException $e) {
       return NULL;
     }
     catch (ResourceNotFoundException $e) {
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/RouterTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/RouterTest.php
index 25d3de0..2ce0f87 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Routing/RouterTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/RouterTest.php
@@ -149,6 +149,20 @@ public function testUrlGeneratorFront() {
   }
 
   /**
+   * Tests that a page trying to match a path will succeed.
+   */
+  public function testRouterMatching() {
+    $this->drupalGet('router_test/test14/1');
+    $this->assertResponse(200);
+    $this->assertText('User route "user.view" was matched.');
+
+    // Try to match a route for a non-existent user.
+    $this->drupalGet('router_test/test14/2');
+    $this->assertResponse(200);
+    $this->assertText('Route not matched.');
+  }
+
+  /**
    * Tests the user account on the DIC.
    */
   public function testUserAccount() {
diff --git a/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/TestControllers.php b/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/TestControllers.php
index 86342d5..0db6826 100644
--- a/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/TestControllers.php
+++ b/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/TestControllers.php
@@ -7,6 +7,9 @@
 
 namespace Drupal\router_test;
 
+use Drupal\Core\ParamConverter\ParamNotConvertedException;
+use Drupal\user\UserInterface;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\HttpFoundation\Response;
 
 /**
@@ -50,4 +53,17 @@ public function test8() {
     return new Response('test8');
   }
 
+  public function test9($uid) {
+    $text = 'Route not matched.';
+    try {
+      $match = \Drupal::service('router')->match('/user/' . $uid);
+      if (isset($match['user']) && $match['user'] instanceof UserInterface) {
+        $text = sprintf('User route "%s" was matched.', $match[RouteObjectInterface::ROUTE_NAME]);
+      }
+    }
+    catch (ParamNotConvertedException $e) {
+    }
+    return new Response($text);
+  }
+
 }
diff --git a/core/modules/system/tests/modules/router_test_directory/router_test.routing.yml b/core/modules/system/tests/modules/router_test_directory/router_test.routing.yml
index 7ad0607..d0460a1 100644
--- a/core/modules/system/tests/modules/router_test_directory/router_test.routing.yml
+++ b/core/modules/system/tests/modules/router_test_directory/router_test.routing.yml
@@ -86,3 +86,10 @@ router_test.13:
   defaults:
     _content: '\Drupal\router_test\TestContent::testAccount'
 
+router_test.14:
+  path: '/router_test/test14/{uid}'
+  requirements:
+    _access: 'TRUE'
+  defaults:
+    _controller: '\Drupal\router_test\TestControllers::test9'
+
diff --git a/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php b/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php
index 0d13959..0e830e2 100644
--- a/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php
@@ -65,7 +65,7 @@ class AccessManagerTest extends UnitTestCase {
   /**
    * The parameter converter.
    *
-   * @var \PHPUnit_Framework_MockObject_MockObject
+   * @var \Drupal\Core\ParamConverter\ParamConverterManagerInterface|\PHPUnit_Framework_MockObject_MockObject
    */
   protected $paramConverter;
 
@@ -119,7 +119,7 @@ protected function setUp() {
       ->method('generate')
       ->will($this->returnValueMap($map));
 
-    $this->paramConverter = $this->getMock('\Drupal\Core\ParamConverter\ParamConverterManager');
+    $this->paramConverter = $this->getMock('Drupal\Core\ParamConverter\ParamConverterManagerInterface');
 
     $this->account = $this->getMock('Drupal\Core\Session\AccountInterface');
 
@@ -395,11 +395,11 @@ public function testCheckNamedRoute() {
     $this->accessManager->setRequest(new Request());
 
     $this->paramConverter->expects($this->at(0))
-      ->method('enhance')
+      ->method('convert')
       ->will($this->returnValue(array()));
 
     $this->paramConverter->expects($this->at(1))
-      ->method('enhance')
+      ->method('convert')
       ->will($this->returnValue(array()));
 
     // Tests the access with routes with parameters without given request.
@@ -431,9 +431,9 @@ public function testCheckNamedRouteWithUpcastedValues() {
       ->method('generate')
       ->will($this->returnValueMap($map));
 
-    $this->paramConverter = $this->getMock('\Drupal\Core\ParamConverter\ParamConverterManager');
+    $this->paramConverter = $this->getMock('Drupal\Core\ParamConverter\ParamConverterManagerInterface');
     $this->paramConverter->expects($this->at(0))
-      ->method('enhance')
+      ->method('convert')
       ->will($this->returnValue(array('value' => 'upcasted_value')));
 
 
@@ -491,9 +491,9 @@ public function testCheckNamedRouteWithDefaultValue() {
       ->with('test_route_1', array('value' => 'example'))
       ->will($this->returnValueMap($map));
 
-    $this->paramConverter = $this->getMock('\Drupal\Core\ParamConverter\ParamConverterManager');
+    $this->paramConverter = $this->getMock('Drupal\Core\ParamConverter\ParamConverterManagerInterface');
     $this->paramConverter->expects($this->at(0))
-      ->method('enhance')
+      ->method('convert')
       ->with(array('value' => 'example', RouteObjectInterface::ROUTE_OBJECT => $route))
       ->will($this->returnValue(array('value' => 'upcasted_value')));
 
diff --git a/core/tests/Drupal/Tests/Core/Enhancer/ParamConversionEnhancerTest.php b/core/tests/Drupal/Tests/Core/Enhancer/ParamConversionEnhancerTest.php
new file mode 100644
index 0000000..5106ff7
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Enhancer/ParamConversionEnhancerTest.php
@@ -0,0 +1,108 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Enhancer\ParamConversionEnhancerTest.
+ */
+
+namespace Drupal\Tests\Core\Enhancer;
+
+use Drupal\Core\Routing\Enhancer\ParamConversionEnhancer;
+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;
+
+/**
+ * Tests the parameter conversion enhancer.
+ *
+ * @coversDefaultClass \Drupal\Core\Routing\Enhancer\ParamConversionEnhancer
+ */
+class ParamConversionEnhancerTest extends UnitTestCase {
+
+  /**
+   * @var \Drupal\Core\Routing\Enhancer\ParamConversionEnhancer
+   */
+  protected $paramConversionEnhancer;
+
+  /**
+   * @var \Drupal\Core\ParamConverter\ParamConverterManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $paramConverterManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Parameter conversion enhancer',
+      'description' => 'Tests the parameter conversion enhancer.',
+      'group' => 'Routing',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->paramConverterManager = $this->getMock('Drupal\Core\ParamConverter\ParamConverterManagerInterface');
+    $this->paramConversionEnhancer = new ParamConversionEnhancer($this->paramConverterManager);
+  }
+
+  /**
+   * @covers ::enhance()
+   */
+  public function testEnhance() {
+    $route = new Route('/test/{id}/{literal}/{null}');
+
+    $raw_variables = array(
+      'id' => 1,
+      'literal' => 'this is a literal',
+      'null' => NULL,
+    );
+    $defaults = array(
+      RouteObjectInterface::ROUTE_OBJECT => $route,
+    ) + $raw_variables;
+
+    $expected = $defaults;
+    $expected['id'] = 'something_better!';
+    $expected['_raw_variables'] = new ParameterBag($raw_variables);
+
+    $this->paramConverterManager->expects($this->any())
+      ->method('convert')
+      ->with($this->isType('array'), $this->isInstanceOf('Symfony\Component\HttpFoundation\Request'))
+      ->will($this->returnValue($expected));
+
+    $result = $this->paramConversionEnhancer->enhance($defaults, new Request());
+
+    $this->assertEquals($expected, $result);
+  }
+
+  /**
+   * @covers ::copyRawVariables()
+   */
+  public function testCopyRawVariables() {
+    $route = new Route('/test/{id}');
+    $defaults = array(
+      RouteObjectInterface::ROUTE_OBJECT => $route,
+      'id' => '1',
+    );
+    // Set one default to mirror another by reference.
+    $defaults['bar'] = &$defaults['id'];
+    $this->paramConverterManager->expects($this->any())
+      ->method('convert')
+      ->with($this->isType('array'), $this->isInstanceOf('Symfony\Component\HttpFoundation\Request'))
+      ->will($this->returnCallback(function ($defaults) {
+        // Convert the mirrored default to another value.
+        $defaults['bar'] = '2';
+        return $defaults;
+      }));
+    $expected = new ParameterBag(array('id' => 1));
+    $result = $this->paramConversionEnhancer->enhance($defaults, new Request());
+    $this->assertEquals($result['_raw_variables'], $expected);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/ParamConverter/ParamConverterManagerTest.php b/core/tests/Drupal/Tests/Core/ParamConverter/ParamConverterManagerTest.php
index 71671a0..c369582 100644
--- a/core/tests/Drupal/Tests/Core/ParamConverter/ParamConverterManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/ParamConverter/ParamConverterManagerTest.php
@@ -10,15 +10,30 @@
 use Drupal\Core\ParamConverter\ParamConverterManager;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
-use Symfony\Component\DependencyInjection\ContainerBuilder;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
 
 /**
  * Tests the typed data resolver manager.
+ *
+ * @coversDefaultClass \Drupal\Core\ParamConverter\ParamConverterManager
  */
 class ParamConverterManagerTest extends UnitTestCase {
 
+  /**
+   * @var \Symfony\Component\DependencyInjection\ContainerBuilder|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $container;
+
+  /**
+   * @var \Drupal\Core\ParamConverter\ParamConverterManager
+   */
+  protected $manager;
+
+  /**
+   * {@inheritdoc}
+   */
   public static function getInfo() {
     return array(
       'name' => 'Parameter converter manager',
@@ -27,10 +42,13 @@ public static function getInfo() {
     );
   }
 
+  /**
+   * {@inheritdoc}
+   */
   public function setUp() {
     parent::setUp();
 
-    $this->container = new ContainerBuilder();
+    $this->container = $this->getMock('Drupal\Core\DependencyInjection\Container');
     $this->manager = new ParamConverterManager();
     $this->manager->setContainer($this->container);
   }
@@ -40,7 +58,8 @@ public function setUp() {
    *
    * @dataProvider providerTestAddConverter
    *
-   * @see ParamConverterManagerTest::providerTestAddConverter().
+   * @covers ::addConverter()
+   * @covers ::getConverterIds()
    */
   public function testAddConverter($unsorted, $sorted) {
     foreach ($unsorted as $data) {
@@ -59,7 +78,7 @@ public function testAddConverter($unsorted, $sorted) {
    *
    * @dataProvider providerTestGetConverter
    *
-   * @see ParamConverterManagerTest::providerTestGetConverter().
+   * @covers ::getConverter()
    */
   public function testGetConverter($name, $priority, $class) {
     $converter = $this->getMockBuilder('Drupal\Core\ParamConverter\ParamConverterInterface')
@@ -67,15 +86,22 @@ public function testGetConverter($name, $priority, $class) {
       ->getMock();
 
     $this->manager->addConverter($name, $priority);
-    $this->container->set($name, $converter);
+    $this->container->expects($this->once())
+      ->method('get')
+      ->with($name)
+      ->will($this->returnValue($converter));
 
     $this->assertInstanceOf($class, $this->manager->getConverter($name));
+    // Assert that a second call to getConverter() does not use the container.
+    $this->assertInstanceOf($class, $this->manager->getConverter($name));
   }
 
   /**
    * Tests \Drupal\Core\ParamConverter\ParamConverterManager::getConverter().
    *
-   * @expectedException InvalidArgumentException
+   * @covers ::getConverter()
+   *
+   * @expectedException \InvalidArgumentException
    */
   public function testGetConverterException() {
     $this->manager->getConverter('undefined.converter');
@@ -146,48 +172,142 @@ public function providerTestGetConverter() {
   }
 
   /**
-   * Tests the enhance method.
+   * @covers ::setRouteParameterConverters()
    *
-   * @see \Drupal\Core\ParamConverter\ParamConverterManager::enhance().
+   * @dataProvider providerTestSetRouteParameterConverters
    */
-  public function testEnhance() {
-    // Create a mock route using a mock parameter converter.
+  public function testSetRouteParameterConverters($path, $parameters = NULL, $expected = NULL) {
     $converter = $this->getMock('Drupal\Core\ParamConverter\ParamConverterInterface');
+    $converter->expects($this->any())
+      ->method('applies')
+      ->with($this->anything(), 'id', $this->anything())
+      ->will($this->returnValue(TRUE));
+    $this->manager->addConverter('applied');
+    $this->container->expects($this->any())
+      ->method('get')
+      ->with('applied')
+      ->will($this->returnValue($converter));
+
+    $route = new Route($path);
+    if ($parameters) {
+      $route->setOption('parameters', $parameters);
+    }
+    $collection = new RouteCollection();
+    $collection->add('test_route', $route);
+
+    $this->manager->setRouteParameterConverters($collection);
+    foreach ($collection as $route) {
+      $result = $route->getOption('parameters');
+      if ($expected) {
+        $this->assertSame($expected, $result['id']['converter']);
+      }
+      else {
+        $this->assertNull($result);
+      }
+    }
+  }
+
+  /**
+   * Provides data for testSetRouteParameterConverters().
+   */
+  public function providerTestSetRouteParameterConverters() {
+    return array(
+      array('/test'),
+      array('/test/{id}', array('id' => array()), 'applied'),
+      array('/test/{id}', array('id' => array('converter' => 'predefined')), 'predefined'),
+    );
+  }
+
+  /**
+   * @covers ::convert()
+   */
+  public function testConvert() {
+    $route = new Route('/test/{id}/{literal}/{null}');
+    $parameters = array(
+      'id' => array(
+        'converter' => 'test_convert',
+      ),
+      'literal' => array(),
+      'null' => array(),
+    );
+    $route->setOption('parameters', $parameters);
+
+    $defaults = array(
+      RouteObjectInterface::ROUTE_OBJECT => $route,
+      RouteObjectInterface::ROUTE_NAME => 'test_route',
+      'id' => 1,
+      'literal' => 'this is a literal',
+      'null' => NULL,
+    );
+
+    $expected = $defaults;
+    $expected['id'] = 'something_better!';
+
+    $converter = $this->getMock('Drupal\Core\ParamConverter\ParamConverterInterface');
+    $converter->expects($this->any())
+      ->method('convert')
+      ->with(1, $this->isType('array'), 'id', $this->isType('array'), $this->isInstanceOf('Symfony\Component\HttpFoundation\Request'))
+      ->will($this->returnValue('something_better!'));
     $this->manager->addConverter('test_convert');
+    $this->container->expects($this->once())
+      ->method('get')
+      ->with('test_convert')
+      ->will($this->returnValue($converter));
 
-    $this->container->set('test_convert', $converter);
+    $result = $this->manager->convert($defaults, new Request());
 
+    $this->assertEquals($expected, $result);
+  }
+
+  /**
+   * @covers ::convert()
+   */
+  public function testConvertNoConverting() {
+    $route = new Route('/test');
+    $defaults = array(
+      RouteObjectInterface::ROUTE_OBJECT => $route,
+      RouteObjectInterface::ROUTE_NAME => 'test_route',
+    );
+
+    $expected = $defaults;
+
+    $result = $this->manager->convert($defaults, new Request());
+    $this->assertEquals($expected, $result);
+  }
+
+  /**
+   * @covers ::convert()
+   *
+   * @expectedException \Drupal\Core\ParamConverter\ParamNotConvertedException
+   * @expectedExceptionMessage The "id" parameter was not converted for the path "/test/{id}" (route name: "test_route")
+   */
+  public function testConvertMissingParam() {
     $route = new Route('/test/{id}');
-    $parameters = array();
-    $parameters['id'] = array(
-      'converter' => 'test_convert'
+    $parameters = array(
+      'id' => array(
+        'converter' => 'test_convert',
+      ),
     );
     $route->setOption('parameters', $parameters);
 
-    $defaults = array();
-    $defaults[RouteObjectInterface::ROUTE_OBJECT] = $route;
-    $defaults['id'] = 1;
-    $defaults['_entity'] = &$defaults['id'];
+    $defaults = array(
+      RouteObjectInterface::ROUTE_OBJECT => $route,
+      RouteObjectInterface::ROUTE_NAME => 'test_route',
+      'id' => 1,
+    );
 
-    $request = new Request();
-
-    $entity = $this->getMockBuilder('\Drupal\user\Entity\User')
-      ->disableOriginalConstructor()
-      ->getMock();
-
-    $converter->expects($this->once())
+    $converter = $this->getMock('Drupal\Core\ParamConverter\ParamConverterInterface');
+    $converter->expects($this->any())
       ->method('convert')
-      ->with($this->equalTo(1))
-      ->will($this->returnValue($entity));
+      ->with(1, $this->isType('array'), 'id', $this->isType('array'), $this->isInstanceOf('Symfony\Component\HttpFoundation\Request'))
+      ->will($this->returnValue(NULL));
+    $this->manager->addConverter('test_convert');
+    $this->container->expects($this->once())
+      ->method('get')
+      ->with('test_convert')
+      ->will($this->returnValue($converter));
 
-    $defaults = $this->manager->enhance($defaults, $request);
-
-    // The value of 1 should be upcast to the User object for UID 1.
-    $this->assertSame($entity, $defaults['id']);
-    // The parameter for the user ID should be stored in the raw variables.
-    $this->assertTrue($defaults['_raw_variables']->has('id'));
-    // The raw non-upcasted value for the user should be the UID.
-    $this->assertEquals(1, $defaults['_raw_variables']->get('id'));
+    $this->manager->convert($defaults, new Request());
   }
 
 }
