diff --git a/core/lib/Drupal/Component/Annotation/Plugin.php b/core/lib/Drupal/Component/Annotation/Plugin.php
index b3699bf..78650dc 100644
--- a/core/lib/Drupal/Component/Annotation/Plugin.php
+++ b/core/lib/Drupal/Component/Annotation/Plugin.php
@@ -39,10 +39,8 @@ class Plugin implements AnnotationInterface {
    */
   public function __construct($values) {
     $reflection = new \ReflectionClass($this);
-    // Only keep actual default values by ignoring NULL values.
-    $defaults = array_filter($reflection->getDefaultProperties(), function ($value) {
-      return $value !== NULL;
-    });
+    $defaults = $reflection->getDefaultProperties();
+    unset($defaults['definition']);
     $parsed_values = $this->parse($values);
     $this->definition = NestedArray::mergeDeep($defaults, $parsed_values);
   }
diff --git a/core/lib/Drupal/Core/Routing/RouteCompiler.php b/core/lib/Drupal/Core/Routing/RouteCompiler.php
index 1d223ff..639feff 100644
--- a/core/lib/Drupal/Core/Routing/RouteCompiler.php
+++ b/core/lib/Drupal/Core/Routing/RouteCompiler.php
@@ -43,7 +43,9 @@ public static function compile(Route $route) {
     $stripped_path = static::getPathWithoutDefaults($route);
     $fit = static::getFit($stripped_path);
     $pattern_outline = static::getPatternOutline($stripped_path);
-    $num_parts = count(explode('/', trim($pattern_outline, '/')));
+    // We count the number of parts including any optional trailing parts. This
+    // allows the RouteProvider to filter candidate routes more efficiently.
+    $num_parts = count(explode('/', trim($route->getPath(), '/')));
 
     return new CompiledRoute(
       $fit,
diff --git a/core/lib/Drupal/Core/Routing/RouteProvider.php b/core/lib/Drupal/Core/Routing/RouteProvider.php
index 278b486..20e9022 100644
--- a/core/lib/Drupal/Core/Routing/RouteProvider.php
+++ b/core/lib/Drupal/Core/Routing/RouteProvider.php
@@ -298,11 +298,9 @@ public function getRoutesByPattern($pattern) {
    *   Returns a route collection of matching routes.
    */
   protected function getRoutesByPath($path) {
-    // Filter out each empty value, though allow '0' and 0, which would be
-    // filtered out by empty().
-    $parts = array_values(array_filter(explode('/', $path), function($value) {
-      return $value !== NULL && $value !== '';
-    }));
+    // Split the path up on the slashes, ignoring multiple slashes in a row
+    // or leading or trailing slashes.
+    $parts = preg_split('@/+@', $path, NULL, PREG_SPLIT_NO_EMPTY);
 
     $collection = new RouteCollection();
 
@@ -311,22 +309,37 @@ protected function getRoutesByPath($path) {
       return $collection;
     }
 
-    $routes = $this->connection->query("SELECT name, route FROM {" . $this->connection->escapeTable($this->tableName) . "} WHERE pattern_outline IN ( :patterns[] ) ORDER BY fit DESC, name ASC", array(
-      ':patterns[]' => $ancestors,
+    // The >= check on number_parts allows us to match routes with optional
+    // trailing wildcard parts as long as the pattern matches, since we
+    // dump the route pattern without those optional parts.
+    $routes = $this->connection->query("SELECT name, route, fit FROM {" . $this->connection->escapeTable($this->tableName) . "} WHERE pattern_outline IN ( :patterns[] ) AND number_parts >= :count_parts", array(
+      ':patterns[]' => $ancestors, ':count_parts' => count($parts),
     ))
-      ->fetchAllKeyed();
+      ->fetchAll(\PDO::FETCH_ASSOC);
 
-    foreach ($routes as $name => $route) {
-      $route = unserialize($route);
-      if (preg_match($route->compile()->getRegex(), $path, $matches)) {
-        $collection->add($name, $route);
-      }
+    // We sort by fit and name in PHP to avoid a SQL filesort.
+    usort($routes, array($this, 'routeProviderRouteCompare'));
+
+    foreach ($routes as $row) {
+      $collection->add($row['name'], unserialize($row['route']));
     }
 
     return $collection;
   }
 
   /**
+   * Comparison function for usort on routes.
+   */
+  public function routeProviderRouteCompare(array $a, array $b) {
+    if ($a['fit'] == $b['fit']) {
+      return strcmp($a['name'], $b['name']);
+    }
+    // Reverse sort from highest to lowest fit. PHP should cast to int, but
+    // the explicit cast makes this sort more robust against unexpected input.
+    return (int) $a['fit'] < (int) $b['fit'] ? 1 : -1;
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function getAllRoutes() {
diff --git a/core/modules/system/src/Tests/Routing/RouteProviderTest.php b/core/modules/system/src/Tests/Routing/RouteProviderTest.php
index 74cbaa3..8be53ff 100644
--- a/core/modules/system/src/Tests/Routing/RouteProviderTest.php
+++ b/core/modules/system/src/Tests/Routing/RouteProviderTest.php
@@ -316,7 +316,46 @@ function testOutlinePathMatchDefaultsCollision2() {
       $this->assertEqual(array('narf', 'poink'), array_keys($routes_array), 'Ensure the fitness was taken into account.');
       $this->assertNotNull($routes->get('narf'), 'The first matching route was found.');
       $this->assertNotNull($routes->get('poink'), 'The second matching route was found.');
-      $this->assertNull($routes->get('eep'), 'Noin-matching route was not found.');
+      $this->assertNull($routes->get('eep'), 'Non-matching route was not found.');
+    }
+    catch (ResourceNotFoundException $e) {
+      $this->fail('No matching route found with default argument value.');
+    }
+  }
+
+  /**
+   * Confirms that we can find multiple routes that match the request equally.
+   */
+  function testOutlinePathMatchDefaultsCollision3() {
+    $connection = Database::getConnection();
+    $provider = new RouteProvider($connection, $this->state, $this->currentPath, $this->cache, $this->pathProcessor, 'test_routes');
+
+    $this->fixtures->createTables($connection);
+
+    $collection = new RouteCollection();
+    $collection->add('poink', new Route('/some/{value}/path'));
+    // Add a second route matching the same path pattern.
+    $collection->add('poink2', new Route('/some/{object}/path'));
+    $collection->add('narf', new Route('/some/here/path'));
+    $collection->add('eep', new Route('/something/completely/different'));
+
+    $dumper = new MatcherDumper($connection, $this->state, 'test_routes');
+    $dumper->addRoutes($collection);
+    $dumper->dump();
+
+    $path = '/some/over-there/path';
+
+    $request = Request::create($path, 'GET');
+
+    try {
+      $routes = $provider->getRouteCollectionForRequest($request);
+      $routes_array = $routes->all();
+
+      $this->assertEqual(count($routes), 2, 'The correct number of routes was found.');
+      $this->assertEqual(array('poink', 'poink2'), array_keys($routes_array), 'Ensure the fitness and name were taken into account in the sort.');
+      $this->assertNotNull($routes->get('poink'), 'The first matching route was found.');
+      $this->assertNotNull($routes->get('poink2'), 'The second matching route was found.');
+      $this->assertNull($routes->get('eep'), 'Non-matching route was not found.');
     }
     catch (ResourceNotFoundException $e) {
       $this->fail('No matching route found with default argument value.');
diff --git a/core/modules/system/src/Tests/Routing/RouterTest.php b/core/modules/system/src/Tests/Routing/RouterTest.php
index b945676..16f4234 100644
--- a/core/modules/system/src/Tests/Routing/RouterTest.php
+++ b/core/modules/system/src/Tests/Routing/RouterTest.php
@@ -197,6 +197,15 @@ public function testRouterMatching() {
     $this->drupalGet('router_test/test14/2');
     $this->assertResponse(200);
     $this->assertText('Route not matched.');
+
+    // Check that very long paths don't cause an error.
+    $path = 'router_test/test1';
+    $suffix = '/d/r/u/p/a/l';
+    for ($i = 0; $i < 10; $i++) {
+      $path .= $suffix;
+      $this->drupalGet($path);
+      $this->assertResponse(404);
+    }
   }
 
   /**
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index a0849b1..d2c1b40 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -946,7 +946,7 @@ function system_schema() {
       ),
     ),
     'indexes' => array(
-      'pattern_outline_fit' => array('pattern_outline', 'fit'),
+      'pattern_outline_parts' => array('pattern_outline', 'number_parts'),
     ),
     'primary key' => array('name'),
   );
@@ -1085,3 +1085,11 @@ function system_schema() {
 
   return $schema;
 }
+
+/**
+ * Change the index on the {router} table.
+ */
+function system_update_8001() {
+  db_drop_index('router', 'pattern_outline_fit');
+  db_add_index('router', 'pattern_outline_parts', array('pattern_outline', 'number_parts'));
+}
diff --git a/core/tests/Drupal/Tests/Component/Annotation/PluginTest.php b/core/tests/Drupal/Tests/Component/Annotation/PluginTest.php
new file mode 100644
index 0000000..ba21799
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/Annotation/PluginTest.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Component\Annotation\PluginTest.
+ */
+
+namespace Drupal\Tests\Component\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Component\Annotation\Plugin
+ *
+ * @group Annotation
+ */
+class PluginTest extends UnitTestCase {
+
+  /**
+   * The annotation values.
+   *
+   * @var mixed[]
+   */
+  protected $annotationValue = [];
+
+  /**
+   * The class under test.
+   *
+   * @var \Drupal\Component\Annotation\Plugin
+   */
+  protected $sut;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->annotationValue = [
+      'id' => $this->randomMachineName(),
+      'class' => $this->getMockClass('\stdClass'),
+      // Test an arbitrary annotation key.
+      $this->randomMachineName() => $this->randomMachineName(),
+      // Test a NULL value.
+      'null' => NULL,
+      'provider' => $this->randomMachineName(),
+    ];
+
+    $this->sut = new Plugin($this->annotationValue);
+  }
+
+  /**
+   * @covers ::__construct
+   * @covers ::parse
+   */
+  public function testConstruct() {
+    $this->sut = new Plugin($this->annotationValue);
+  }
+
+  /**
+   * @covers ::get
+   */
+  public function testGet() {
+    $definition = $this->sut->get();
+    // The SUT's default implementation returns array plugin definitions.
+    $this->assertInternalType('array', $definition);
+    // The $definition property is not part of the annotation, but is an
+    // implementation detail instead. As such, it must not appear in the plugin
+    // definition.
+    $this->assertArrayNotHasKey('definition', $definition);
+
+    foreach ($this->annotationValue as $key => $value) {
+      $this->assertSame($value, $definition[$key]);
+    }
+  }
+
+  /**
+   * @covers ::getId
+   */
+  public function testGetId() {
+    $this->assertSame($this->annotationValue['id'], $this->sut->getId());
+  }
+
+  /**
+   * @covers ::getClass
+   * @covers ::setClass
+   */
+  public function testGetClass() {
+    $this->assertSame($this->annotationValue['class'], $this->sut->getClass());
+    $class = $this->getMockClass('\stdClass');
+    $this->sut->setClass($class);
+    $this->assertSame($class, $this->sut->getClass());
+  }
+
+  /**
+   * @covers ::getProvider
+   * @covers ::setProvider
+   */
+  public function testGetProvider() {
+    $this->assertSame($this->annotationValue['provider'], $this->sut->getProvider());
+    $provider = $this->randomMachineName();
+    $this->sut->setProvider($provider);
+    $this->assertSame($provider, $this->sut->getProvider());
+  }
+
+}
