diff --git a/core/core.services.yml b/core/core.services.yml
index 8e9ca23..a01cc63 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -255,7 +255,9 @@ services:
       - [fromRequest, ['@request']]
   router.route_provider:
     class: Drupal\Core\Routing\RouteProvider
-    arguments: ['@database']
+    arguments: ['@database', '@router.builder']
+    tags:
+      - { name: event_subscriber }
   router.matcher.final_matcher:
     class: Drupal\Core\Routing\UrlMatcher
   router.matcher:
@@ -300,7 +302,12 @@ services:
     arguments: ['@database']
   router.builder:
     class: Drupal\Core\Routing\RouteBuilder
-    arguments: ['@router.dumper', '@lock', '@event_dispatcher', '@module_handler', '@controller_resolver']
+    arguments: ['@router.dumper', '@lock', '@event_dispatcher', '@module_handler', '@controller_resolver', '@state']
+  router.rebuild_subscriber:
+    class: Drupal\Core\EventSubscriber\RouterRebuildSubscriber
+    arguments: ['@router.builder', '@cache.cache']
+    tags:
+      - { name: event_subscriber }
   path.alias_manager.cached:
     class: Drupal\Core\CacheDecorator\AliasManagerCacheDecorator
     arguments: ['@path.alias_manager', '@cache.path']
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 0099008..07c69f1 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -4992,7 +4992,6 @@ function drupal_flush_all_caches() {
   // Important: This rebuild must happen last, so the menu router is guaranteed
   // to be based on up to date information.
   \Drupal::service('router.builder')->rebuild();
-  menu_router_rebuild();
 
   // Re-initialize the maintenance theme, if the current request attempted to
   // use it. Unlike regular usages of this function, the installer and update
diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 513849a..824709b 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -475,13 +475,7 @@ function menu_get_item($path = NULL, $router_item = NULL) {
     $router_items[$path] = $router_item;
   }
   if (!isset($router_items[$path])) {
-    // Rebuild if we know it's needed, or if the menu masks are missing which
-    // occurs rarely, likely due to a race condition of multiple rebuilds.
-    if (\Drupal::state()->get('menu_rebuild_needed') || !\Drupal::state()->get('menu.masks')) {
-      menu_router_rebuild();
-      \Drupal::service('router.builder')->rebuild();
-      \Drupal::cache()->deleteTags(array('local_task' => 1));
-    }
+    \Drupal::service('router.builder')->rebuildIfNeeded();
     $original_map = arg(NULL, $path);
 
     $parts = array_slice($original_map, 0, MENU_MAX_PARTS);
@@ -2538,13 +2532,13 @@ function menu_router_rebuild() {
   $transaction = db_transaction();
 
   try {
+    // Ensure the route based router is up to date.
+    \Drupal::service('router.builder')->rebuildIfNeeded();
     list($menu) = menu_router_build(TRUE);
     _menu_navigation_links_rebuild($menu);
     // Clear the menu, page and block caches.
     menu_cache_clear_all();
     _menu_clear_page_cache();
-    // Indicate that the menu has been successfully rebuilt.
-    \Drupal::state()->delete('menu_rebuild_needed');
   }
   catch (Exception $e) {
     $transaction->rollback();
@@ -2746,6 +2740,9 @@ function _menu_navigation_links_rebuild($menu) {
   }
 
   // Find any item whose router path does not exist any more.
+  // Do not delete entries with an empty path as this can remove menu links in
+  // the process of being created.
+  $paths[] = '';
   $query = \Drupal::entityQuery('menu_link')
     ->condition('router_path', $paths, 'NOT IN')
     ->condition('external', 0)
diff --git a/core/lib/Drupal/Core/EventSubscriber/RouterRebuildSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/RouterRebuildSubscriber.php
new file mode 100644
index 0000000..28addb8
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/RouterRebuildSubscriber.php
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\EventSubscriber\RouterRebuildSubscriber.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Routing\RouteBuilderInterface;
+use Drupal\Core\Routing\RoutingEvents;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpKernel\KernelEvents;
+
+/**
+ * Rebuilds the router and menu_router if necessary.
+ */
+class RouterRebuildSubscriber implements EventSubscriberInterface {
+
+  /**
+   * @var \Drupal\Core\Routing\RouteBuilderInterface
+   */
+  protected $routeBuilder;
+
+  /**
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cacheBackend;
+
+  /**
+   * @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder
+   *   The route builder.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   */
+  public function __construct(RouteBuilderInterface $route_builder, CacheBackendInterface $cache_backend) {
+    $this->routeBuilder = $route_builder;
+    $this->cacheBackend = $cache_backend;
+  }
+
+  /**
+   * Rebuilds routers if necessary.
+   *
+   * @param \Symfony\Component\EventDispatcher\Event $event
+   *   The event object.
+   */
+  public function onKernelTerminate(Event $event) {
+    $this->routeBuilder->rebuildIfNeeded();
+  }
+
+  public function onRouterRebuild(Event $event) {
+    menu_router_rebuild();
+    $this->cacheBackend->deleteTags(array('local_task' => 1));
+  }
+
+  /**
+   * Registers the methods in this class that should be listeners.
+   *
+   * @return array
+   *   An array of event listener definitions.
+   */
+  static function getSubscribedEvents() {
+    $events[KernelEvents::TERMINATE][] = array('onKernelTerminate', 200);
+    $events[RoutingEvents::FINISHED][] = array('onRouterRebuild', 200);
+    return $events;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php
index 40e59cb..e73f984 100644
--- a/core/lib/Drupal/Core/Extension/ThemeHandler.php
+++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php
@@ -75,7 +75,7 @@ class ThemeHandler implements ThemeHandlerInterface {
    *
    * @var \Drupal\Core\Routing\RouteBuilder
    */
-  protected $routerBuilder;
+  protected $routeBuilder;
 
   /**
    * The system listing info
@@ -456,7 +456,7 @@ protected function configInstallDefaultConfig($theme) {
    */
   protected function resetSystem() {
     if ($this->routeBuilder) {
-      $this->routeBuilder->rebuild();
+      $this->routeBuilder->setRebuildNeeded();
     }
     $this->systemListReset();
 
diff --git a/core/lib/Drupal/Core/Routing/RouteBuilder.php b/core/lib/Drupal/Core/Routing/RouteBuilder.php
index 76a826b..3260710 100644
--- a/core/lib/Drupal/Core/Routing/RouteBuilder.php
+++ b/core/lib/Drupal/Core/Routing/RouteBuilder.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Discovery\YamlDiscovery;
 use Drupal\Core\Controller\ControllerResolverInterface;
+use Drupal\Core\KeyValueStore\StateInterface;
 use Symfony\Component\EventDispatcher\Event;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 use Symfony\Component\Routing\RouteCollection;
@@ -23,7 +24,7 @@
  * Because this class makes use of the modules system, it cannot currently
  * be unit tested.
  */
-class RouteBuilder {
+class RouteBuilder implements RouteBuilderInterface {
 
   /**
    * The dumper to which we should send collected routes.
@@ -68,7 +69,7 @@ class RouteBuilder {
   protected $controllerResolver;
 
   /**
-   * Construcs the RouteBuilder using the passed MatcherDumperInterface.
+   * Constructs the RouteBuilder using the passed MatcherDumperInterface.
    *
    * @param \Drupal\Core\Routing\MatcherDumperInterface $dumper
    *   The matcher dumper used to store the route information.
@@ -81,19 +82,17 @@ class RouteBuilder {
    * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
    *   The controller resolver.
    */
-  public function __construct(MatcherDumperInterface $dumper, LockBackendInterface $lock, EventDispatcherInterface $dispatcher, ModuleHandlerInterface $module_handler, ControllerResolverInterface $controller_resolver) {
+  public function __construct(MatcherDumperInterface $dumper, LockBackendInterface $lock, EventDispatcherInterface $dispatcher, ModuleHandlerInterface $module_handler, ControllerResolverInterface $controller_resolver, StateInterface $state = NULL) {
     $this->dumper = $dumper;
     $this->lock = $lock;
     $this->dispatcher = $dispatcher;
     $this->moduleHandler = $module_handler;
     $this->controllerResolver = $controller_resolver;
+    $this->state = $state;
   }
 
   /**
-   * Rebuilds the route info and dumps to dumper.
-   *
-   * @return bool
-   *   Returns TRUE if the rebuild succeeds, FALSE otherwise.
+   * {@inheritdoc}
    */
   public function rebuild() {
     if (!$this->lock->acquire('router_rebuild')) {
@@ -149,12 +148,30 @@ public function rebuild() {
     $this->dumper->addRoutes($collection);
     $this->dumper->dump(array('provider' => 'dynamic_routes'));
 
+    $this->state->delete(static::REBUILD_NEEDED);
     $this->lock->release('router_rebuild');
     $this->dispatcher->dispatch(RoutingEvents::FINISHED, new Event());
     return TRUE;
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function rebuildIfNeeded() {
+    if ($this->state->get(static::REBUILD_NEEDED, FALSE)) {
+      return $this->rebuild();
+    }
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setRebuildNeeded() {
+    $this->state->set(static::REBUILD_NEEDED, TRUE);
+  }
+
+  /**
    * Returns the YAML discovery for getting all the .routing.yml files.
    *
    * @return \Drupal\Component\Discovery\YamlDiscovery
diff --git a/core/lib/Drupal/Core/Routing/RouteBuilderInterface.php b/core/lib/Drupal/Core/Routing/RouteBuilderInterface.php
new file mode 100644
index 0000000..116ea27
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/RouteBuilderInterface.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Routing\RouteBuilderInterface.
+ */
+
+namespace Drupal\Core\Routing;
+
+interface RouteBuilderInterface {
+
+  const REBUILD_NEEDED = 'router_rebuild_needed';
+
+  /**
+   * Rebuilds the route info and dumps to dumper.
+   *
+   * @return bool
+   *   Returns TRUE if the rebuild succeeds, FALSE otherwise.
+   */
+  public function rebuild();
+
+  /**
+   * Rebuilds the route info and dumps to dumper if necessary.
+   *
+   * @return bool
+   *   Returns TRUE if the rebuild occurs, FALSE otherwise.
+   */
+  public function rebuildIfNeeded();
+
+  /**
+   * Set the router to be rebuilt next next it is needed.
+   */
+  public function setRebuildNeeded();
+
+}
diff --git a/core/lib/Drupal/Core/Routing/RouteBuilderStatic.php b/core/lib/Drupal/Core/Routing/RouteBuilderStatic.php
index d6161f6..9c81ffa 100644
--- a/core/lib/Drupal/Core/Routing/RouteBuilderStatic.php
+++ b/core/lib/Drupal/Core/Routing/RouteBuilderStatic.php
@@ -10,14 +10,31 @@
 /**
  * This builds a static version of the router.
  */
-class RouteBuilderStatic {
+class RouteBuilderStatic implements RouteBuilderInterface {
 
   /**
-   * Rebuilds router.
+   * @inheritdoc
    */
   public function rebuild() {
     // @todo Add the route for the batch pages when that conversion happens,
     //   http://drupal.org/node/1987816.
   }
 
+  /**
+   * @inheritdoc
+   */
+  public function rebuildIfNeeded(){
+    // @todo Add the route for the batch pages when that conversion happens,
+    //   http://drupal.org/node/1987816.
+  }
+
+  /**
+   * @inheritdoc
+   */
+  public function setRebuildNeeded() {
+    // @todo Add the route for the batch pages when that conversion happens,
+    //   http://drupal.org/node/1987816.
+
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Routing/RouteProvider.php b/core/lib/Drupal/Core/Routing/RouteProvider.php
index 18dc506..43aa271 100644
--- a/core/lib/Drupal/Core/Routing/RouteProvider.php
+++ b/core/lib/Drupal/Core/Routing/RouteProvider.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Routing;
 
 use Drupal\Component\Utility\String;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Exception\RouteNotFoundException;
 use Symfony\Component\Routing\RouteCollection;
@@ -18,7 +19,7 @@
 /**
  * A Route Provider front-end for all Drupal-stored routes.
  */
-class RouteProvider implements RouteProviderInterface {
+class RouteProvider implements RouteProviderInterface, EventSubscriberInterface {
 
   /**
    * The database connection from which to read route information.
@@ -35,6 +36,13 @@ class RouteProvider implements RouteProviderInterface {
   protected $tableName;
 
   /**
+   * The route builder.
+   *
+   * @var \Drupal\Core\Routing\RouteBuilderInterface
+   */
+  protected $routeBuilder;
+
+  /**
    * A cache of already-loaded routes, keyed by route name.
    *
    * @var array
@@ -46,11 +54,14 @@ class RouteProvider implements RouteProviderInterface {
    *
    * @param \Drupal\Core\Database\Connection $connection
    *   A database connection object.
+   * @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder
+   *   The route builder.
    * @param string $table
    *   The table in the database to use for matching.
    */
-  public function __construct(Connection $connection, $table = 'router') {
+  public function __construct(Connection $connection, RouteBuilderInterface $route_builder, $table = 'router') {
     $this->connection = $connection;
+    $this->routeBuilder = $route_builder;
     $this->tableName = $table;
   }
 
@@ -99,7 +110,12 @@ public function getRouteCollectionForRequest(Request $request) {
 
     $collection = $this->getRoutesByPath($path);
 
-    if (!count($collection)) {
+    // Try rebuilding the router if it is necessary.
+    if (!$collection->count() && $this->routeBuilder->rebuildIfNeeded()) {
+      $collection = $this->getRoutesByPath($path);
+    }
+
+    if (!$collection->count()) {
       throw new ResourceNotFoundException(String::format("The route for '@path' could not be found", array('@path' => $path)));
     }
 
@@ -269,4 +285,19 @@ public function getAllRoutes() {
     return new LazyLoadingRouteCollection($this->connection, $this->tableName);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function resetStaticCache() {
+    $this->routes  = array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  static function getSubscribedEvents() {
+    $events[RoutingEvents::FINISHED][] = array('resetStaticCache');
+    return $events;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Routing/RouteProviderInterface.php b/core/lib/Drupal/Core/Routing/RouteProviderInterface.php
index f84a19b..2a02277 100644
--- a/core/lib/Drupal/Core/Routing/RouteProviderInterface.php
+++ b/core/lib/Drupal/Core/Routing/RouteProviderInterface.php
@@ -40,4 +40,9 @@ public function getRoutesByPattern($pattern);
    */
   public function getAllRoutes();
 
+  /**
+   * Resets the static route cache.
+   */
+  public function resetStaticCache();
+
 }
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockHiddenRegionTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockHiddenRegionTest.php
index 208710e..3fa85d6 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockHiddenRegionTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockHiddenRegionTest.php
@@ -64,8 +64,9 @@ public function testBlockNotInHiddenRegion() {
     \Drupal::config('system.theme')
       ->set('default', $theme)
       ->save();
-    \Drupal::service('router.builder')->rebuild();
-    menu_router_rebuild();
+    // Enabling a theme will cause the kernel terminate event to rebuild the
+    // router. Simulate that here.
+    \Drupal::service('router.builder')->rebuildIfNeeded();
 
     // Ensure that "block_test_theme" is set as the default theme.
     $this->drupalGet('admin/structure/block');
diff --git a/core/modules/color/lib/Drupal/color/Tests/ColorTest.php b/core/modules/color/lib/Drupal/color/Tests/ColorTest.php
index 4193152..a22b003 100644
--- a/core/modules/color/lib/Drupal/color/Tests/ColorTest.php
+++ b/core/modules/color/lib/Drupal/color/Tests/ColorTest.php
@@ -48,8 +48,6 @@ function setUp() {
       ),
     );
     theme_enable(array_keys($this->themes));
-    $this->container->get('router.builder')->rebuild();
-    menu_router_rebuild();
 
     // Array filled with valid and not valid color values
     $this->colorTests = array(
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php b/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php
index c6e4094..393db33 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php
@@ -669,8 +669,9 @@ public function testThemeDiscovery() {
     // Enable the test theme and rebuild routes.
     $theme = 'config_translation_test_theme';
     theme_enable(array($theme));
-    \Drupal::service('router.builder')->rebuild();
-    menu_router_rebuild();
+    // Enabling a theme will cause the kernel terminate event to rebuild the
+    // router. Simulate that here.
+    \Drupal::service('router.builder')->rebuildIfNeeded();
 
     $this->drupalLogin($this->admin_user);
 
diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module
index cdcc9be..8605757 100644
--- a/core/modules/field_ui/field_ui.module
+++ b/core/modules/field_ui/field_ui.module
@@ -129,7 +129,7 @@ function field_ui_entity_info(&$entity_info) {
 function field_ui_entity_bundle_create($entity_type, $bundle) {
   // When a new bundle is created, the menu needs to be rebuilt to add our
   // menu item tabs.
-  \Drupal::state()->set('menu_rebuild_needed', TRUE);
+  \Drupal::service('router.builder')->setRebuildNeeded();
 }
 
 /**
@@ -138,7 +138,7 @@ function field_ui_entity_bundle_create($entity_type, $bundle) {
 function field_ui_entity_bundle_rename($entity_type, $bundle_old, $bundle_new) {
   // When a bundle is renamed, the menu needs to be rebuilt to add our
   // menu item tabs.
-  \Drupal::state()->set('menu_rebuild_needed', TRUE);
+  \Drupal::service('router.builder')->setRebuildNeeded();
 }
 
 /**
@@ -236,14 +236,14 @@ function field_ui_library_info() {
  * Implements hook_view_mode_presave().
  */
 function field_ui_view_mode_presave(EntityViewModeInterface $view_mode) {
-  \Drupal::state()->set('menu_rebuild_needed', TRUE);
+  \Drupal::service('router.builder')->setRebuildNeeded();
 }
 
 /**
  * Implements hook_view_mode_delete().
  */
 function field_ui_view_mode_delete(EntityViewModeInterface $view_mode) {
-  \Drupal::state()->set('menu_rebuild_needed', TRUE);
+  \Drupal::service('router.builder')->setRebuildNeeded();
 }
 
 /**
diff --git a/core/modules/file/lib/Drupal/file/Tests/Views/ExtensionViewsFieldTest.php b/core/modules/file/lib/Drupal/file/Tests/Views/ExtensionViewsFieldTest.php
index 87ab5f8..9683c1f 100644
--- a/core/modules/file/lib/Drupal/file/Tests/Views/ExtensionViewsFieldTest.php
+++ b/core/modules/file/lib/Drupal/file/Tests/Views/ExtensionViewsFieldTest.php
@@ -18,7 +18,7 @@ class ExtensionViewsFieldTest extends ViewUnitTestBase {
   /**
    * {@inheritdoc}
    */
-  public static $modules = array('file', 'file_test_views');
+  public static $modules = array('file', 'file_test_views', 'user');
 
   /**
    * Views used by this test.
diff --git a/core/modules/menu/menu.install b/core/modules/menu/menu.install
index b7dc67d..2a61238 100644
--- a/core/modules/menu/menu.install
+++ b/core/modules/menu/menu.install
@@ -13,7 +13,6 @@
 function menu_install() {
   // Add a link for each custom menu.
   \Drupal::service('router.builder')->rebuild();
-  menu_router_rebuild();
   $system_link = entity_load_multiple_by_properties('menu_link', array('link_path' => 'admin/structure/menu', 'module' => 'system'));
   $system_link = reset($system_link);
 
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/Entity/MenuLink.php b/core/modules/menu_link/lib/Drupal/menu_link/Entity/MenuLink.php
index 638f47f..fd998d0 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/Entity/MenuLink.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/Entity/MenuLink.php
@@ -542,7 +542,7 @@ public function preSave(EntityStorageControllerInterface $storage_controller) {
     }
     // Find the route_name.
     if (!isset($this->route_name)) {
-      if ($result = \Drupal::service('router.matcher.final_matcher')->findRouteNameParameters($this->link_path)) {
+      if (!$this->external && $result = \Drupal::service('router.matcher.final_matcher')->findRouteNameParameters($this->link_path)) {
         list($this->route_name, $this->route_parameters) = $result;
       }
       else {
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
index 0e95f42..61b4b6c 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
@@ -121,7 +121,7 @@ public function save(EntityInterface $entity) {
       }
 
       if ($entity->isNew()) {
-        $entity->mlid = $this->database->insert($this->entityInfo->getBaseTable())->fields(array('menu_name' => 'tools'))->execute();
+        $entity->mlid = $this->database->insert($this->entityInfo->getBaseTable())->fields(array('menu_name' => $entity->menu_name))->execute();
         $entity->enforceIsNew();
       }
 
diff --git a/core/modules/search/lib/Drupal/search/Entity/SearchPage.php b/core/modules/search/lib/Drupal/search/Entity/SearchPage.php
index d0419d4..d40900d 100644
--- a/core/modules/search/lib/Drupal/search/Entity/SearchPage.php
+++ b/core/modules/search/lib/Drupal/search/Entity/SearchPage.php
@@ -205,11 +205,7 @@ public function preSave(EntityStorageControllerInterface $storage_controller) {
    */
   public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
     parent::postSave($storage_controller, $update);
-
-    $this->state()->set('menu_rebuild_needed', TRUE);
-    // @todo The above call should be sufficient, but it is not until
-    //   https://drupal.org/node/2167323 is fixed.
-    \Drupal::service('router.builder')->rebuild();
+    $this->routeBuilder()->setRebuildNeeded();
   }
 
   /**
@@ -239,13 +235,13 @@ public static function sort($a, $b) {
   }
 
   /**
-   * Wraps the state storage.
+   * Wraps the route builder.
    *
-   * @return \Drupal\Core\KeyValueStore\StateInterface
+   * @return \Drupal\Core\Routing\RouteBuilderInterface
    *   An object for state storage.
    */
-  protected function state() {
-    return \Drupal::state();
+  protected function routeBuilder() {
+    return \Drupal::service('router.builder');
   }
 
   /**
diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchKeywordsConditionsTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchKeywordsConditionsTest.php
index 15ae685..5f6e0bc 100644
--- a/core/modules/search/lib/Drupal/search/Tests/SearchKeywordsConditionsTest.php
+++ b/core/modules/search/lib/Drupal/search/Tests/SearchKeywordsConditionsTest.php
@@ -36,7 +36,6 @@ function setUp() {
     $this->drupalLogin($this->searching_user);
     // Test with all search modules enabled.
     \Drupal::config('search.settings')->set('active_plugins', array('node_search', 'user_search', 'search_extra_type_search'))->save();
-    \Drupal::state()->set('menu_rebuild_needed', TRUE);
   }
 
   /**
diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchPageOverrideTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchPageOverrideTest.php
index 0f371e1..d916207 100644
--- a/core/modules/search/lib/Drupal/search/Tests/SearchPageOverrideTest.php
+++ b/core/modules/search/lib/Drupal/search/Tests/SearchPageOverrideTest.php
@@ -38,11 +38,9 @@ function setUp() {
 
     // Enable the extra type module for searching.
     \Drupal::config('search.settings')->set('active_plugins', array('node_search', 'user_search', 'search_extra_type_search'))->save();
-    \Drupal::service('router.builder')->rebuild();
   }
 
   function testSearchPageHook() {
-    $this->container->get('router.builder')->rebuild();
     $keys = 'bike shed ' . $this->randomName();
     $this->drupalGet("search/dummy_path/{$keys}");
     $this->assertText('Dummy search snippet', 'Dummy search snippet is shown');
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
index 560afb1..b2c53ae 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
@@ -790,6 +790,12 @@ public function run(array $methods = array()) {
           );
           $completion_check_id = TestBase::insertAssert($this->testId, $class, FALSE, t('The test did not complete due to a fatal error.'), 'Completion check', $caller);
           $this->setUp();
+          // If set up is broken we can get here without a container.
+          // @see \Drupal\simpletest\Tests\BrokenSetUpTest
+          if ($this->container instanceOf ContainerBuilder && $this->container->has('router.builder')) {
+            $this->fail('Router rebuild required after set up!');
+            $this->container->get('router.builder')->rebuildIfNeeded();
+          }
           if ($this->setup) {
             try {
               $this->$method();
diff --git a/core/modules/system/lib/Drupal/system/Entity/Menu.php b/core/modules/system/lib/Drupal/system/Entity/Menu.php
index fa5d7fd..7ac7bf1 100644
--- a/core/modules/system/lib/Drupal/system/Entity/Menu.php
+++ b/core/modules/system/lib/Drupal/system/Entity/Menu.php
@@ -8,6 +8,7 @@
 namespace Drupal\system\Entity;
 
 use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\system\MenuInterface;
 
 /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php
index ccc327d..6d0fee8 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php
@@ -186,7 +186,6 @@ protected function doTestMenuName() {
     // rebuild.
     menu_test_menu_name('changed');
     \Drupal::service('router.builder')->rebuild();
-    menu_router_rebuild();
 
     $menu_links = entity_load_multiple_by_properties('menu_link', array('router_path' => 'menu_name_test'));
     $menu_link = reset($menu_links);
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/RebuildTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/RebuildTest.php
index 8c01748..bf1464e 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/RebuildTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/RebuildTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Menu;
 
+use Drupal\Core\Routing\RouteBuilderInterface;
 use Drupal\simpletest\WebTestBase;
 
 /**
@@ -36,9 +37,10 @@ function testMenuRebuildByVariable() {
     $admin_exists = db_query('SELECT path from {menu_router} WHERE path = :path', array(':path' => 'admin'))->fetchField();
     $this->assertFalse($admin_exists, "The path 'admin/' has been deleted and doesn't exist in the database.");
 
-    // Now we enable the rebuild variable and send a request to rebuild the menu
+    // Now we set the rebuild state flag and send a request to rebuild the menu
     // item. Now 'admin' should exist.
-    \Drupal::state()->set('menu_rebuild_needed', TRUE);
+    \Drupal::state()->set(RouteBuilderInterface::REBUILD_NEEDED, TRUE);
+
     // The request should trigger the rebuild.
     $this->drupalGet('<front>');
     $admin_exists = db_query('SELECT path from {menu_router} WHERE path = :path', array(':path' => 'admin'))->fetchField();
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/MockRouteProvider.php b/core/modules/system/lib/Drupal/system/Tests/Routing/MockRouteProvider.php
index de56a90..20b9bef 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Routing/MockRouteProvider.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/MockRouteProvider.php
@@ -82,4 +82,11 @@ public function getAllRoutes() {
     return $this->routes->all();
   }
 
+  /**
+   * @inheritdoc}
+   */
+  public function resetStaticCache() {
+    $this->routes = array();
+  }
+
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/RouteProviderTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/RouteProviderTest.php
index bae0909..b143797 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Routing/RouteProviderTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/RouteProviderTest.php
@@ -18,6 +18,7 @@
 use Drupal\Core\Database\Database;
 use Drupal\Core\Routing\MatcherDumper;
 use Drupal\Tests\Core\Routing\RoutingFixtures;
+use Drupal\Tests\Core\Routing\NullRouteBuilder;
 
 /**
  * Basic tests for the RouteProvider.
@@ -31,6 +32,13 @@ class RouteProviderTest extends UnitTestBase {
    */
   protected $fixtures;
 
+  /**
+   * A null route builder to enable testing of the route provider.
+   *
+   * @var \Drupal\Core\Routing\RouteBuilderInterface
+   */
+  protected $routeBuilder;
+
   public static function getInfo() {
     return array(
       'name' => 'Route Provider tests',
@@ -43,6 +51,7 @@ function __construct($test_id = NULL) {
     parent::__construct($test_id);
 
     $this->fixtures = new RoutingFixtures();
+    $this->routeBuilder = new NullRouteBuilder();
   }
 
   public function tearDown() {
@@ -57,7 +66,7 @@ public function tearDown() {
   public function testCandidateOutlines() {
 
     $connection = Database::getConnection();
-    $provider = new RouteProvider($connection);
+    $provider = new RouteProvider($connection, $this->routeBuilder);
 
     $parts = array('node', '5', 'edit');
 
@@ -81,7 +90,7 @@ public function testCandidateOutlines() {
    */
   function testExactPathMatch() {
     $connection = Database::getConnection();
-    $provider = new RouteProvider($connection, 'test_routes');
+    $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes');
 
     $this->fixtures->createTables($connection);
 
@@ -105,7 +114,7 @@ function testExactPathMatch() {
    */
   function testOutlinePathMatch() {
     $connection = Database::getConnection();
-    $provider = new RouteProvider($connection, 'test_routes');
+    $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes');
 
     $this->fixtures->createTables($connection);
 
@@ -134,7 +143,7 @@ function testOutlinePathMatch() {
    */
   function testOutlinePathMatchTrailingSlash() {
     $connection = Database::getConnection();
-    $provider = new RouteProvider($connection, 'test_routes');
+    $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes');
 
     $this->fixtures->createTables($connection);
 
@@ -163,7 +172,7 @@ function testOutlinePathMatchTrailingSlash() {
    */
   function testOutlinePathMatchDefaults() {
     $connection = Database::getConnection();
-    $provider = new RouteProvider($connection, 'test_routes');
+    $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes');
 
     $this->fixtures->createTables($connection);
 
@@ -201,7 +210,7 @@ function testOutlinePathMatchDefaults() {
    */
   function testOutlinePathMatchDefaultsCollision() {
     $connection = Database::getConnection();
-    $provider = new RouteProvider($connection, 'test_routes');
+    $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes');
 
     $this->fixtures->createTables($connection);
 
@@ -240,7 +249,7 @@ function testOutlinePathMatchDefaultsCollision() {
    */
   function testOutlinePathMatchDefaultsCollision2() {
     $connection = Database::getConnection();
-    $provider = new RouteProvider($connection, 'test_routes');
+    $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes');
 
     $this->fixtures->createTables($connection);
 
@@ -279,7 +288,7 @@ function testOutlinePathMatchDefaultsCollision2() {
    */
   public function testOutlinePathMatchZero() {
     $connection = Database::getConnection();
-    $provider = new RouteProvider($connection, 'test_routes');
+    $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes');
 
     $this->fixtures->createTables($connection);
 
@@ -314,7 +323,7 @@ public function testOutlinePathMatchZero() {
    */
   function testOutlinePathNoMatch() {
     $connection = Database::getConnection();
-    $provider = new RouteProvider($connection, 'test_routes');
+    $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes');
 
     $this->fixtures->createTables($connection);
 
@@ -343,7 +352,7 @@ function testOutlinePathNoMatch() {
    */
   function testSystemPathMatch() {
     $connection = Database::getConnection();
-    $provider = new RouteProvider($connection, 'test_routes');
+    $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes');
 
     $this->fixtures->createTables($connection);
 
@@ -368,7 +377,7 @@ function testSystemPathMatch() {
    */
   protected function testRouteByName() {
     $connection = Database::getConnection();
-    $provider = new RouteProvider($connection, 'test_routes');
+    $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes');
 
     $this->fixtures->createTables($connection);
 
@@ -403,7 +412,7 @@ protected function testRouteByName() {
    */
   public function testGetRoutesByPatternWithLongPatterns() {
     $connection = Database::getConnection();
-    $provider = new RouteProvider($connection, 'test_routes');
+    $provider = new RouteProvider($connection, $this->routeBuilder, 'test_routes');
 
     $this->fixtures->createTables($connection);
 
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index 697f8c7..035555e 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -35,8 +35,7 @@ function system_theme_default() {
       // implementations, and doing the variable_set() before the theme_enable()
       // could result in a race condition where the theme is default but not
       // enabled.
-      \Drupal::service('router.builder')->rebuild();
-      menu_router_rebuild();
+      \Drupal::service('router.builder')->setRebuildNeeded();
       \Drupal::cache('cache')->deleteTags(array('local_task' => 1));
 
       // The status message depends on whether an admin theme is currently in use:
diff --git a/core/modules/views/lib/Drupal/views/EventSubscriber/RouteSubscriber.php b/core/modules/views/lib/Drupal/views/EventSubscriber/RouteSubscriber.php
index 9540364..4188c47 100644
--- a/core/modules/views/lib/Drupal/views/EventSubscriber/RouteSubscriber.php
+++ b/core/modules/views/lib/Drupal/views/EventSubscriber/RouteSubscriber.php
@@ -181,6 +181,7 @@ protected function alterRoutes(RouteCollection $collection, $provider) {
    * {@inheritdoc}
    */
   public function routeRebuildFinished() {
+    $this->reset();
     $this->state->set('views.view_route_names', $this->viewRouteNames);
   }
 
diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterBooleanOperatorStringTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterBooleanOperatorStringTest.php
index 3617409..d3c3862 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterBooleanOperatorStringTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterBooleanOperatorStringTest.php
@@ -49,7 +49,7 @@ public static function getInfo() {
   protected function setUp() {
     parent::setUp();
 
-    $this->installSchema('system', array('router', 'menu_router', 'variable', 'key_value_expire'));
+    $this->installSchema('system', array('menu_router', 'variable', 'key_value_expire'));
   }
 
 
diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterBooleanOperatorTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterBooleanOperatorTest.php
index 8ff7823..77841f3 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterBooleanOperatorTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterBooleanOperatorTest.php
@@ -45,7 +45,7 @@ public static function getInfo() {
   protected function setUp() {
     parent::setUp();
 
-    $this->installSchema('system', array('router', 'menu_router', 'variable', 'key_value_expire'));
+    $this->installSchema('system', array('menu_router', 'variable', 'key_value_expire'));
   }
 
   /**
diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterDateTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterDateTest.php
index 72b7b21..36b8d58 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterDateTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterDateTest.php
@@ -151,7 +151,7 @@ protected function _testBetween() {
   protected function _testUiValidation() {
 
     $this->drupalLogin($this->drupalCreateUser(array('administer views', 'administer site configuration')));
-    menu_router_rebuild();
+
     $this->drupalGet('admin/structure/views/view/test_filter_date_between/edit');
     $this->drupalGet('admin/structure/views/nojs/config-item/test_filter_date_between/default/filter/created');
 
diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterEqualityTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterEqualityTest.php
index 4ab42ad..9b74533 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterEqualityTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterEqualityTest.php
@@ -38,7 +38,7 @@ public static function getInfo() {
   protected function setUp() {
     parent::setUp();
 
-    $this->installSchema('system', array('router', 'menu_router', 'variable', 'key_value_expire'));
+    $this->installSchema('system', array('menu_router', 'variable', 'key_value_expire'));
   }
 
   function viewsData() {
diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterInOperatorTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterInOperatorTest.php
index 6e3e4e5..0c1f32a 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterInOperatorTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterInOperatorTest.php
@@ -39,7 +39,7 @@ public static function getInfo() {
   protected function setUp() {
     parent::setUp();
 
-    $this->installSchema('system', array('router', 'menu_router', 'variable', 'key_value_expire'));
+    $this->installSchema('system', array('menu_router', 'variable', 'key_value_expire'));
   }
 
   function viewsData() {
diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterNumericTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterNumericTest.php
index 62b16d3..a1f50ef 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterNumericTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterNumericTest.php
@@ -39,7 +39,7 @@ public static function getInfo() {
   protected function setUp() {
     parent::setUp();
 
-    $this->installSchema('system', array('router', 'menu_router', 'variable', 'key_value_expire'));
+    $this->installSchema('system', array('menu_router', 'variable', 'key_value_expire'));
   }
 
   function viewsData() {
diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterStringTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterStringTest.php
index cb444c9..f00eaf3 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Handler/FilterStringTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Handler/FilterStringTest.php
@@ -38,7 +38,7 @@ public static function getInfo() {
   protected function setUp() {
     parent::setUp();
 
-    $this->installSchema('system', array('router', 'menu_router', 'variable', 'key_value_expire'));
+    $this->installSchema('system', array('menu_router', 'variable', 'key_value_expire'));
   }
 
   function viewsData() {
diff --git a/core/modules/views/lib/Drupal/views/Tests/Handler/HandlerTest.php b/core/modules/views/lib/Drupal/views/Tests/Handler/HandlerTest.php
index 91d9654..a347d47 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Handler/HandlerTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Handler/HandlerTest.php
@@ -76,7 +76,6 @@ protected function viewsData() {
   function testFilterInOperatorUi() {
     $admin_user = $this->drupalCreateUser(array('administer views', 'administer site configuration'));
     $this->drupalLogin($admin_user);
-    menu_router_rebuild();
 
     $path = 'admin/structure/views/nojs/config-item/test_filter_in_operator_ui/default/filter/type';
     $this->drupalGet($path);
diff --git a/core/modules/views/lib/Drupal/views/Tests/Plugin/AccessTest.php b/core/modules/views/lib/Drupal/views/Tests/Plugin/AccessTest.php
index 1532b40..9fe91bb 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Plugin/AccessTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/AccessTest.php
@@ -86,7 +86,9 @@ function testStaticAccessPlugin() {
     $display['display_options']['access']['options']['access'] = TRUE;
     $access_plugin->options['access'] = TRUE;
     $view->save();
-    $this->container->get('router.builder')->rebuild();
+    // Saving a view will cause the router to be rebuilt when the kernel
+    // termination event fires. Simulate that here.
+    $this->container->get('router.builder')->rebuildIfNeeded();
 
     $this->assertTrue($access_plugin->access($this->normal_user));
 
diff --git a/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayAttachmentTest.php b/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayAttachmentTest.php
index c5d6255..7e55039 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayAttachmentTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayAttachmentTest.php
@@ -40,9 +40,6 @@ protected function setUp() {
    * Tests the attachment plugin.
    */
   protected function testAttachment() {
-    // @todo Remove that once http://drupal.org/node/1828444 got in.
-    \Drupal::state()->set('menu_rebuild_needed', TRUE);
-
     $this->drupalGet('test-display-attachment');
 
     $result = $this->xpath('//div[contains(@class, "view-content")]');
diff --git a/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayPageTest.php b/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayPageTest.php
index b0d830b..41f5dc5 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayPageTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayPageTest.php
@@ -7,12 +7,9 @@
 
 namespace Drupal\views\Tests\Plugin;
 
-use Drupal\Core\Routing\RouteBuildEvent;
-use Drupal\views\EventSubscriber\RouteSubscriber;
 use Drupal\views\Tests\ViewUnitTestBase;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpKernel\HttpKernelInterface;
-use Symfony\Component\Routing\RouteCollection;
 
 /**
  * Tests the page display plugin.
@@ -57,7 +54,7 @@ protected function setUp() {
     parent::setUp();
 
     // Setup the needed tables in order to make the drupal router working.
-    $this->installSchema('system', array('router', 'menu_router', 'url_alias'));
+    $this->installSchema('system', array('menu_router', 'url_alias'));
     $this->installSchema('menu_link', 'menu_links');
   }
 
@@ -65,9 +62,6 @@ protected function setUp() {
    * Checks the behavior of the page for access denied/not found behaviours.
    */
   public function testPageResponses() {
-    // @todo Importing a route should fire a container rebuild.
-    $this->container->get('router.builder')->rebuild();
-
     $subrequest = Request::create('/test_page_display_403', 'GET');
     $response = $this->container->get('http_kernel')->handle($subrequest, HttpKernelInterface::SUB_REQUEST);
     $this->assertEqual($response->getStatusCode(), 403);
@@ -87,6 +81,9 @@ public function testPageResponses() {
     $view = views_get_view('test_page_display');
     // Disable the view, rebuild menu, and request the page again.
     $view->storage->disable()->save();
+    // Router rebuild would occur in a kernel terminate event so we need to
+    // simulate that here.
+    \Drupal::service('router.builder')->rebuildIfNeeded();
 
     $response = $this->container->get('http_kernel')->handle($subrequest, HttpKernelInterface::SUB_REQUEST);
     $this->assertEqual($response->getStatusCode(), 404);
diff --git a/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayTest.php b/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayTest.php
index 0ea9565..e527d52 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayTest.php
@@ -250,6 +250,7 @@ public function testInvalidDisplayPlugins() {
 
     // Rebuild the router, and ensure that the path is not accessible anymore.
     views_invalidate_cache();
+    \Drupal::service('router.builder')->rebuildIfNeeded();
 
     $this->drupalGet('test_display_invalid');
     $this->assertResponse(404);
diff --git a/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayUnitTest.php b/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayUnitTest.php
index 6315015..aa7306c 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayUnitTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayUnitTest.php
@@ -25,7 +25,7 @@ class DisplayUnitTest extends ViewUnitTestBase {
    *
    * @var array
    */
-  public static $modules = array('block', 'node');
+  public static $modules = array('block', 'node', 'field', 'user');
 
   /**
    * Views plugin types to test.
diff --git a/core/modules/views/lib/Drupal/views/Tests/Plugin/ExposedFormTest.php b/core/modules/views/lib/Drupal/views/Tests/Plugin/ExposedFormTest.php
index e3e3db0..2ad29c9 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Plugin/ExposedFormTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/ExposedFormTest.php
@@ -67,8 +67,6 @@ public function testSubmitButton() {
     $view->display_handler->setOption('exposed_form', $exposed_form);
     $view->save();
 
-    views_invalidate_cache();
-
     // Make sure the submit button label changed.
     $this->drupalGet('test_exposed_form_buttons');
     $this->helperButtonHasLabel('edit-submit-test-exposed-form-buttons', $expected_label);
@@ -82,8 +80,6 @@ public function testSubmitButton() {
     $view->display_handler->setOption('exposed_form', $exposed_form);
     $view->save();
 
-    views_invalidate_cache();
-
     // Make sure the submit button label shows 'Apply'.
     $this->drupalGet('test_exposed_form_buttons');
     $this->helperButtonHasLabel('edit-submit-test-exposed-form-buttons', t('Apply'));
@@ -120,8 +116,6 @@ public function testResetButton() {
     $view->display_handler->setOption('exposed_form', $exposed_form);
     $view->save();
 
-    views_invalidate_cache();
-
     // Look whether the reset button label changed.
     $this->drupalGet('test_exposed_form_buttons', array('query' => array('type' => 'article')));
     $this->assertResponse(200);
diff --git a/core/modules/views/lib/Drupal/views/Tests/Plugin/MiniPagerTest.php b/core/modules/views/lib/Drupal/views/Tests/Plugin/MiniPagerTest.php
index b18e0d3..10955d4 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Plugin/MiniPagerTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/MiniPagerTest.php
@@ -49,7 +49,6 @@ protected function setUp() {
    * Tests the rendering of mini pagers.
    */
   public function testMiniPagerRender() {
-    menu_router_rebuild();
     $this->drupalGet('test_mini_pager');
     $this->assertText('›› test', 'The next link appears on the first page.');
     $this->assertText('Page 1', 'The current page info shows the first page.');
diff --git a/core/modules/views/lib/Drupal/views/Tests/Plugin/RowEntityTest.php b/core/modules/views/lib/Drupal/views/Tests/Plugin/RowEntityTest.php
index d71c283..ede97bd 100644
--- a/core/modules/views/lib/Drupal/views/Tests/Plugin/RowEntityTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/RowEntityTest.php
@@ -51,7 +51,7 @@ public static function getInfo() {
   protected function setUp() {
     parent::setUp();
 
-    $this->installSchema('system', array('menu_router', 'router'));
+    $this->installSchema('system', array('menu_router'));
     $this->installSchema('taxonomy', array('taxonomy_term_data', 'taxonomy_term_hierarchy'));
     $this->installConfig(array('taxonomy'));
   }
diff --git a/core/modules/views/lib/Drupal/views/Tests/ViewUnitTestBase.php b/core/modules/views/lib/Drupal/views/Tests/ViewUnitTestBase.php
index e5ec825..620e4fe 100644
--- a/core/modules/views/lib/Drupal/views/Tests/ViewUnitTestBase.php
+++ b/core/modules/views/lib/Drupal/views/Tests/ViewUnitTestBase.php
@@ -54,6 +54,9 @@ protected function setUpFixtures() {
       $this->installSchema('views_test_data', $table);
     }
 
+    // The router table is required for router rebuilds.
+    $this->installSchema('system', array('router'));
+
     // Load the test dataset.
     $data_set = $this->dataSet();
     $query = db_insert('views_test_data')
diff --git a/core/modules/views/views.module b/core/modules/views/views.module
index fe427b0..1a20ac1 100644
--- a/core/modules/views/views.module
+++ b/core/modules/views/views.module
@@ -551,20 +551,13 @@ function views_invalidate_cache() {
   Cache::deleteTags(array('content' => TRUE));
 
   // Set the menu as needed to be rebuilt.
-  \Drupal::state()->set('menu_rebuild_needed', TRUE);
+  \Drupal::service('router.builder')->setRebuildNeeded();
 
   $module_handler = \Drupal::moduleHandler();
 
   // Reset the RouteSubscriber from views.
   \Drupal::getContainer()->get('views.route_subscriber')->reset();
 
-  // Set the router to be rebuild.
-  // @todo Figure out why the cache rebuild is trigged but the route table
-  //   does not exist yet.
-  if (db_table_exists('router')) {
-    \Drupal::service('router.builder')->rebuild();
-  }
-
   // Invalidate the block cache to update views block derivatives.
   if ($module_handler->moduleExists('block')) {
     \Drupal::service('plugin.manager.block')->clearCachedDefinitions();
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/Tests/ExposedFormUITest.php b/core/modules/views_ui/lib/Drupal/views_ui/Tests/ExposedFormUITest.php
index 0df5845..dab40a5 100644
--- a/core/modules/views_ui/lib/Drupal/views_ui/Tests/ExposedFormUITest.php
+++ b/core/modules/views_ui/lib/Drupal/views_ui/Tests/ExposedFormUITest.php
@@ -43,7 +43,6 @@ protected function setUp() {
    * Tests the admin interface of exposed filter and sort items.
    */
   function testExposedAdminUi() {
-    menu_router_rebuild();
     $edit = array();
 
     $this->drupalGet('admin/structure/views/nojs/config-item/test_exposed_admin_ui/default/filter/type');
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/Tests/SettingsTest.php b/core/modules/views_ui/lib/Drupal/views_ui/Tests/SettingsTest.php
index 764bb0b..86029a9 100644
--- a/core/modules/views_ui/lib/Drupal/views_ui/Tests/SettingsTest.php
+++ b/core/modules/views_ui/lib/Drupal/views_ui/Tests/SettingsTest.php
@@ -87,7 +87,7 @@ function testEditUI() {
       'ui_show_display_embed' => FALSE,
     );
     $this->drupalPostForm('admin/structure/views/settings', $edit, t('Save configuration'));
-    views_invalidate_cache();
+
     $this->drupalPostForm('admin/structure/views/add', $view, t('Save and edit'));
     $this->assertNoFieldById('edit-displays-top-add-display-embed');
 
diff --git a/core/tests/Drupal/Tests/Core/Routing/NullRouteBuilder.php b/core/tests/Drupal/Tests/Core/Routing/NullRouteBuilder.php
new file mode 100644
index 0000000..2631c48
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Routing/NullRouteBuilder.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Drupal\Tests\Core\Routing;
+
+use Drupal\Core\Routing\RouteBuilderInterface;
+
+class NullRouteBuilder implements RouteBuilderInterface {
+
+  public function rebuild() {
+  }
+
+  public function rebuildIfNeeded() {
+  }
+
+  public function setRebuildNeeded() {
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Routing/RouteBuilderTest.php b/core/tests/Drupal/Tests/Core/Routing/RouteBuilderTest.php
index 98cbfda..dd15f61 100644
--- a/core/tests/Drupal/Tests/Core/Routing/RouteBuilderTest.php
+++ b/core/tests/Drupal/Tests/Core/Routing/RouteBuilderTest.php
@@ -75,6 +75,13 @@ class RouteBuilderTest extends UnitTestCase {
    */
   protected $controllerResolver;
 
+  /**
+   * The key value store.
+   *
+   * @var \Drupal\Core\KeyValueStore\StateInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $state;
+
   public static function getInfo() {
     return array(
       'name' => 'Route Builder',
@@ -92,8 +99,9 @@ protected function setUp() {
     $this->yamlDiscovery = $this->getMockBuilder('\Drupal\Component\Discovery\YamlDiscovery')
       ->disableOriginalConstructor()
       ->getMock();
+    $this->state = $this->getMock('\Drupal\Core\KeyValueStore\StateInterface');
 
-    $this->routeBuilder = new TestRouteBuilder($this->dumper, $this->lock, $this->dispatcher, $this->moduleHandler, $this->controllerResolver);
+    $this->routeBuilder = new TestRouteBuilder($this->dumper, $this->lock, $this->dispatcher, $this->moduleHandler, $this->controllerResolver, $this->state);
     $this->routeBuilder->setYamlDiscovery($this->yamlDiscovery);
   }
 
@@ -110,6 +118,10 @@ public function testRebuildLockingUnlocking() {
       ->method('release')
       ->with('router_rebuild');
 
+    $this->state->expects($this->once())
+       ->method('delete')
+       ->with('router_rebuild_needed');
+
     $this->yamlDiscovery->expects($this->any())
       ->method('findAll')
       ->will($this->returnValue(array()));
@@ -254,6 +266,44 @@ public function testRebuildWithProviderBasedRoutes() {
     $this->assertTrue($this->routeBuilder->rebuild());
   }
 
+  /**
+   * Tests that the route rebuilding if necessary works.
+   */
+  public function testRebuildIfNecessary() {
+    $this->lock->expects($this->once())
+               ->method('acquire')
+               ->with('router_rebuild')
+               ->will($this->returnValue(TRUE));
+
+    $this->lock->expects($this->once())
+               ->method('release')
+               ->with('router_rebuild');
+
+    $this->state->expects($this->once())
+                ->method('set')
+                ->with('router_rebuild_needed');
+
+    $this->state->expects($this->once())
+                ->method('delete')
+                ->with('router_rebuild_needed');
+
+    $this->state->expects($this->exactly(2))
+                ->method('get')
+                ->with('router_rebuild_needed')
+                ->will($this->onConsecutiveCalls(TRUE, FALSE));
+
+    $this->yamlDiscovery->expects($this->any())
+                        ->method('findAll')
+                        ->will($this->returnValue(array()));
+
+    $this->routeBuilder->setRebuildNeeded();
+
+    // This will trigger a successful rebuild.
+    $this->assertTrue($this->routeBuilder->rebuildIfNeeded());
+
+    // This will not trigger a rebuild.
+    $this->assertFalse($this->routeBuilder->rebuildIfNeeded());
+  }
 }
 
 /**
