diff --git a/core/core.services.yml b/core/core.services.yml
index 5a9cd6e..7e7421c 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -39,6 +39,11 @@ services:
     arguments: ['@request_stack']
     tags:
       - { name: cache.context }
+  cache_context.url.path:
+    class: Drupal\Core\Cache\PathCacheContext
+    arguments: ['@request_stack']
+    tags:
+      - { name: cache.context }
   cache_context.url.query_args:
     class: Drupal\Core\Cache\Context\QueryArgsCacheContext
     arguments: ['@request_stack']
diff --git a/core/lib/Drupal/Core/Breadcrumb/Breadcrumb.php b/core/lib/Drupal/Core/Breadcrumb/Breadcrumb.php
new file mode 100644
index 0000000..1ba5e4a
--- /dev/null
+++ b/core/lib/Drupal/Core/Breadcrumb/Breadcrumb.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Breadcrumb\Breadcrumb.
+ */
+
+namespace Drupal\Core\Breadcrumb;
+
+use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Link;
+
+/**
+ * Used to return generated breadcrumbs with associated cacheability metadata.
+ */
+class Breadcrumb extends CacheableMetadata {
+
+  /**
+   * An array of links for the breadcrumb.
+   *
+   * @var \Drupal\Core\Link[]
+   */
+  protected $links = [];
+
+  /**
+   * Gets the breadcrumb links.
+   *
+   * @return \Drupal\Core\Link[]
+   */
+  public function getLinks() {
+    return $this->links;
+  }
+
+  /**
+   * Sets the breadcrumb links.
+   *
+   * @param \Drupal\Core\Link[] $links
+   *   The breadcrumb links.
+   *
+   * @return $this
+   */
+  public function setLinks(array $links) {
+    $this->links = $links;
+
+    return $this;
+  }
+
+  /**
+   * Appends a link to the end of the breadcrumb.
+   *
+   * @param \Drupal\Core\Link $link
+   *   The link appended to the breadcrumb.
+   *
+   * @return $this
+   */
+  public function addLink(Link $link) {
+    $this->links[] = $link;
+
+    return $this;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Breadcrumb/BreadcrumbBuilderInterface.php b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbBuilderInterface.php
index ebdfa55..e566f54 100644
--- a/core/lib/Drupal/Core/Breadcrumb/BreadcrumbBuilderInterface.php
+++ b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbBuilderInterface.php
@@ -32,9 +32,8 @@ public function applies(RouteMatchInterface $route_match);
    * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
    *   The current route match.
    *
-   * @return \Drupal\Core\Link[]
-   *   An array of links for the breadcrumb. Returning an empty array will
-   *   suppress all breadcrumbs.
+   * @return \Drupal\Core\Breadcrumb\Breadcrumb
+   *   A breadcrumb.
    */
   public function build(RouteMatchInterface $route_match);
 
diff --git a/core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php
index 0099897..a145114 100644
--- a/core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php
+++ b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php
@@ -76,21 +76,30 @@ public function applies(RouteMatchInterface $route_match) {
    * {@inheritdoc}
    */
   public function build(RouteMatchInterface $route_match) {
-    $breadcrumb = array();
+
+    // Without builders return early.
+    $sorted_builders = $this->getSortedBuilders();
+    if (empty($sorted_builders)) {
+      return array();
+    }
+
+    $build = new Breadcrumb();
     $context = array('builder' => NULL);
     // Call the build method of registered breadcrumb builders,
     // until one of them returns an array.
-    foreach ($this->getSortedBuilders() as $builder) {
+    foreach ($sorted_builders as $builder) {
       if (!$builder->applies($route_match)) {
         // The builder does not apply, so we continue with the other builders.
         continue;
       }
 
-      $build = $builder->build($route_match);
+      $breadcrumb = $builder->build($route_match);
 
-      if (is_array($build)) {
-        // The builder returned an array of breadcrumb links.
-        $breadcrumb = $build;
+      if ($breadcrumb instanceof Breadcrumb) {
+        // The builder returned a Breadcrumb object which has to be added to an
+        // array so it could be altered.
+        $build->setCacheContexts($breadcrumb->getCacheContexts());
+        $build->setLinks($breadcrumb->getLinks());
         $context['builder'] = $builder;
         break;
       }
@@ -99,8 +108,8 @@ public function build(RouteMatchInterface $route_match) {
       }
     }
     // Allow modules to alter the breadcrumb.
-    $this->moduleHandler->alter('system_breadcrumb', $breadcrumb, $route_match, $context);
-    // Fall back to an empty breadcrumb.
+    $this->moduleHandler->alter('system_breadcrumb', $build, $route_match, $context);
+
     return $breadcrumb;
   }
 
diff --git a/core/lib/Drupal/Core/Cache/PathCacheContext.php b/core/lib/Drupal/Core/Cache/PathCacheContext.php
new file mode 100644
index 0000000..522f243
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/PathCacheContext.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Cache\PathCacheContext.
+ */
+
+namespace Drupal\Core\Cache;
+
+/**
+ * Defines the PathCacheContext service, for "per URL path" caching.
+ *
+ * (This allows for caching generated relative URLs.)
+ *
+ * @see \Symfony\Component\HttpFoundation\Request::getBasePath()
+ * @see \Symfony\Component\HttpFoundation\Request::getPathInfo()
+ */
+class PathCacheContext extends RequestStackCacheContextBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getLabel() {
+    return t('Path');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContext() {
+    $request = $this->requestStack->getCurrentRequest();
+    return $request->getBasePath() . $request->getPathInfo();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Menu/menu.api.php b/core/lib/Drupal/Core/Menu/menu.api.php
index 1d8298f..65290ff 100644
--- a/core/lib/Drupal/Core/Menu/menu.api.php
+++ b/core/lib/Drupal/Core/Menu/menu.api.php
@@ -533,12 +533,8 @@ function hook_contextual_links_plugins_alter(array &$contextual_links) {
 /**
  * Perform alterations to the breadcrumb built by the BreadcrumbManager.
  *
- * @param array $breadcrumb
- *   An array of breadcrumb link a tags, returned by the breadcrumb manager
- *   build method, for example
- *   @code
- *     array('<a href="/">Home</a>');
- *   @endcode
+ * @param \Drupal\Core\Breadcrumb\Breadcrumb $breadcrumb
+ *   A breadcrumb object returned by the breadcrumb manager build method.
  * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
  *   The current route match.
  * @param array $context
@@ -549,9 +545,9 @@ function hook_contextual_links_plugins_alter(array &$contextual_links) {
  *
  * @ingroup menu
  */
-function hook_system_breadcrumb_alter(array &$breadcrumb, \Drupal\Core\Routing\RouteMatchInterface $route_match, array $context) {
+function hook_system_breadcrumb_alter(Drupal\Core\Breadcrumb\Breadcrumb &$breadcrumb, \Drupal\Core\Routing\RouteMatchInterface $route_match, array $context) {
   // Add an item to the end of the breadcrumb.
-  $breadcrumb[] = Drupal::l(t('Text'), 'example_route_name');
+  $breadcrumb->addLink(Drupal::l(t('Text'), 'example_route_name'));
 }
 
 /**
diff --git a/core/modules/book/book.services.yml b/core/modules/book/book.services.yml
index 3b45dae..b3ad597 100644
--- a/core/modules/book/book.services.yml
+++ b/core/modules/book/book.services.yml
@@ -26,6 +26,8 @@ services:
   cache_context.route.book_navigation:
     class: Drupal\book\Cache\BookNavigationCacheContext
     arguments: ['@request_stack']
+    calls:
+      - [setContainer, ['@service_container']]
     tags:
       - { name: cache.context}
 
diff --git a/core/modules/book/src/BookBreadcrumbBuilder.php b/core/modules/book/src/BookBreadcrumbBuilder.php
index be0e63a..b1ece44 100644
--- a/core/modules/book/src/BookBreadcrumbBuilder.php
+++ b/core/modules/book/src/BookBreadcrumbBuilder.php
@@ -8,6 +8,7 @@
 namespace Drupal\book;
 
 use Drupal\Core\Access\AccessManagerInterface;
+use Drupal\Core\Breadcrumb\Breadcrumb;
 use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Link;
@@ -72,6 +73,8 @@ public function applies(RouteMatchInterface $route_match) {
    */
   public function build(RouteMatchInterface $route_match) {
     $book_nids = array();
+    $breadcrumb = new Breadcrumb();
+
     $links = array(Link::createFromRoute($this->t('Home'), '<front>'));
     $book = $route_match->getParameter('node')->book;
     $depth = 1;
@@ -92,7 +95,9 @@ public function build(RouteMatchInterface $route_match) {
         $depth++;
       }
     }
-    return $links;
+    $breadcrumb->setLinks($links);
+    $breadcrumb->setCacheContexts(['route.book_navigation']);
+    return $breadcrumb;
   }
 
 }
diff --git a/core/modules/comment/src/CommentBreadcrumbBuilder.php b/core/modules/comment/src/CommentBreadcrumbBuilder.php
index 8bc2f25..8c6866f 100644
--- a/core/modules/comment/src/CommentBreadcrumbBuilder.php
+++ b/core/modules/comment/src/CommentBreadcrumbBuilder.php
@@ -8,6 +8,7 @@
 namespace Drupal\comment;
 
 use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
+use Drupal\Core\Breadcrumb\Breadcrumb;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Link;
 use Drupal\Core\Routing\RouteMatchInterface;
@@ -47,19 +48,21 @@ public function applies(RouteMatchInterface $route_match) {
    * {@inheritdoc}
    */
   public function build(RouteMatchInterface $route_match) {
-    $breadcrumb = [Link::createFromRoute($this->t('Home'), '<front>')];
+    $breadcrumb = new Breadcrumb();
+    $breadcrumb->setCacheContexts(['route']);
+    $links = [Link::createFromRoute($this->t('Home'), '<front>')];
 
     $entity = $route_match->getParameter('entity');
-    $breadcrumb[] = new Link($entity->label(), $entity->urlInfo());
+    $links[] = new Link($entity->label(), $entity->urlInfo());
 
     if (($pid = $route_match->getParameter('pid')) && ($comment = $this->storage->load($pid))) {
       /** @var \Drupal\comment\CommentInterface $comment */
       // Display link to parent comment.
       // @todo Clean-up permalink in https://www.drupal.org/node/2198041
-      $breadcrumb[] = new Link($comment->getSubject(), $comment->urlInfo());
+      $links[] = new Link($comment->getSubject(), $comment->urlInfo());
     }
 
-    return $breadcrumb;
+    return $breadcrumb->setLinks($links);
   }
 
 }
diff --git a/core/modules/forum/src/Breadcrumb/ForumBreadcrumbBuilderBase.php b/core/modules/forum/src/Breadcrumb/ForumBreadcrumbBuilderBase.php
index f595ee8..d15478f 100644
--- a/core/modules/forum/src/Breadcrumb/ForumBreadcrumbBuilderBase.php
+++ b/core/modules/forum/src/Breadcrumb/ForumBreadcrumbBuilderBase.php
@@ -8,6 +8,7 @@
 namespace Drupal\forum\Breadcrumb;
 
 use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
+use Drupal\Core\Breadcrumb\Breadcrumb;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Link;
@@ -65,14 +66,17 @@ public function __construct(EntityManagerInterface $entity_manager, ConfigFactor
    * {@inheritdoc}
    */
   public function build(RouteMatchInterface $route_match) {
-    $breadcrumb[] = Link::createFromRoute($this->t('Home'), '<front>');
+    $breadcrumb = new Breadcrumb();
+    $breadcrumb->setCacheContexts(['route']);
+
+    $links[] = Link::createFromRoute($this->t('Home'), '<front>');
 
     $vocabulary = $this->entityManager
       ->getStorage('taxonomy_vocabulary')
       ->load($this->config->get('vocabulary'));
-    $breadcrumb[] = Link::createFromRoute($vocabulary->label(), 'forum.index');
+    $links[] = Link::createFromRoute($vocabulary->label(), 'forum.index');
 
-    return $breadcrumb;
+    return $breadcrumb->setLinks($links);
   }
 
 }
diff --git a/core/modules/forum/src/Breadcrumb/ForumListingBreadcrumbBuilder.php b/core/modules/forum/src/Breadcrumb/ForumListingBreadcrumbBuilder.php
index 9d63772..b446f3e 100644
--- a/core/modules/forum/src/Breadcrumb/ForumListingBreadcrumbBuilder.php
+++ b/core/modules/forum/src/Breadcrumb/ForumListingBreadcrumbBuilder.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\forum\Breadcrumb;
 
+use Drupal\Core\Breadcrumb\Breadcrumb;
 use Drupal\Core\Link;
 use Drupal\Core\Routing\RouteMatchInterface;
 
@@ -27,6 +28,7 @@ public function applies(RouteMatchInterface $route_match) {
    */
   public function build(RouteMatchInterface $route_match) {
     $breadcrumb = parent::build($route_match);
+    $breadcrumb->addCacheContexts(['route']);
 
     // Add all parent forums to breadcrumbs.
     $term_id = $route_match->getParameter('taxonomy_term')->id();
@@ -34,9 +36,9 @@ public function build(RouteMatchInterface $route_match) {
     if ($parents) {
       foreach (array_reverse($parents) as $parent) {
         if ($parent->id() != $term_id) {
-          $breadcrumb[] = Link::createFromRoute($parent->label(), 'forum.page', array(
+          $breadcrumb->addLink(Link::createFromRoute($parent->label(), 'forum.page', array(
             'taxonomy_term' => $parent->id(),
-          ));
+          )));
         }
       }
     }
diff --git a/core/modules/forum/src/Breadcrumb/ForumNodeBreadcrumbBuilder.php b/core/modules/forum/src/Breadcrumb/ForumNodeBreadcrumbBuilder.php
index 0e0bd04..c68a2c6 100644
--- a/core/modules/forum/src/Breadcrumb/ForumNodeBreadcrumbBuilder.php
+++ b/core/modules/forum/src/Breadcrumb/ForumNodeBreadcrumbBuilder.php
@@ -29,18 +29,20 @@ public function applies(RouteMatchInterface $route_match) {
    */
   public function build(RouteMatchInterface $route_match) {
     $breadcrumb = parent::build($route_match);
+    $breadcrumb->addCacheContexts(['route']);
 
     $parents = $this->forumManager->getParents($route_match->getParameter('node')->forum_tid);
     if ($parents) {
       $parents = array_reverse($parents);
       foreach ($parents as $parent) {
-        $breadcrumb[] = Link::createFromRoute($parent->label(), 'forum.page',
+        $breadcrumb->addLink(Link::createFromRoute($parent->label(), 'forum.page',
           array(
             'taxonomy_term' => $parent->id(),
           )
-        );
+        ));
       }
     }
+
     return $breadcrumb;
   }
 
diff --git a/core/modules/forum/tests/src/Unit/Breadcrumb/ForumBreadcrumbBuilderBaseTest.php b/core/modules/forum/tests/src/Unit/Breadcrumb/ForumBreadcrumbBuilderBaseTest.php
index da7ff11..528aceb 100644
--- a/core/modules/forum/tests/src/Unit/Breadcrumb/ForumBreadcrumbBuilderBaseTest.php
+++ b/core/modules/forum/tests/src/Unit/Breadcrumb/ForumBreadcrumbBuilderBaseTest.php
@@ -128,7 +128,7 @@ public function testBuild() {
     );
 
     // And finally, the test.
-    $this->assertEquals($expected, $breadcrumb_builder->build($route_match));
+    $this->assertEquals($expected, $breadcrumb_builder->build($route_match)->getLinks());
   }
 
 }
diff --git a/core/modules/forum/tests/src/Unit/Breadcrumb/ForumListingBreadcrumbBuilderTest.php b/core/modules/forum/tests/src/Unit/Breadcrumb/ForumListingBreadcrumbBuilderTest.php
index 95c670f..4521576 100644
--- a/core/modules/forum/tests/src/Unit/Breadcrumb/ForumListingBreadcrumbBuilderTest.php
+++ b/core/modules/forum/tests/src/Unit/Breadcrumb/ForumListingBreadcrumbBuilderTest.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Link;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Symfony\Component\DependencyInjection\Container;
 
 /**
  * @coversDefaultClass \Drupal\forum\Breadcrumb\ForumListingBreadcrumbBuilder
@@ -171,6 +172,13 @@ public function testBuild() {
       )
     );
 
+    $cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\CacheContextsManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $container = new Container();
+    $container->set('cache_contexts_manager', $cache_contexts_manager);
+    \Drupal::setContainer($container);
+
     // Add a translation manager for t().
     $translation_manager = $this->getStringTranslationStub();
     $breadcrumb_builder->setStringTranslation($translation_manager);
@@ -197,7 +205,7 @@ public function testBuild() {
       Link::createFromRoute('Fora_is_the_plural_of_forum', 'forum.index'),
       Link::createFromRoute('Something', 'forum.page', array('taxonomy_term' => 1)),
     );
-    $this->assertEquals($expected1, $breadcrumb_builder->build($route_match));
+    $this->assertEquals($expected1, $breadcrumb_builder->build($route_match)->getLinks());
 
     // Second test.
     $expected2 = array(
@@ -206,7 +214,7 @@ public function testBuild() {
       Link::createFromRoute('Something else', 'forum.page', array('taxonomy_term' => 2)),
       Link::createFromRoute('Something', 'forum.page', array('taxonomy_term' => 1)),
     );
-    $this->assertEquals($expected2, $breadcrumb_builder->build($route_match));
+    $this->assertEquals($expected2, $breadcrumb_builder->build($route_match)->getLinks());
   }
 
 }
diff --git a/core/modules/forum/tests/src/Unit/Breadcrumb/ForumNodeBreadcrumbBuilderTest.php b/core/modules/forum/tests/src/Unit/Breadcrumb/ForumNodeBreadcrumbBuilderTest.php
index ec5dec0..2b6fd16 100644
--- a/core/modules/forum/tests/src/Unit/Breadcrumb/ForumNodeBreadcrumbBuilderTest.php
+++ b/core/modules/forum/tests/src/Unit/Breadcrumb/ForumNodeBreadcrumbBuilderTest.php
@@ -9,7 +9,7 @@
 
 use Drupal\Core\Link;
 use Drupal\Tests\UnitTestCase;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Symfony\Component\DependencyInjection\Container;
 
 /**
  * @coversDefaultClass \Drupal\forum\Breadcrumb\ForumNodeBreadcrumbBuilder
@@ -179,6 +179,13 @@ public function testBuild() {
       )
     );
 
+    $cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\CacheContextsManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $container = new Container();
+    $container->set('cache_contexts_manager', $cache_contexts_manager);
+    \Drupal::setContainer($container);
+
     // Add a translation manager for t().
     $translation_manager = $this->getStringTranslationStub();
     $property = new \ReflectionProperty('Drupal\forum\Breadcrumb\ForumNodeBreadcrumbBuilder', 'stringTranslation');
@@ -203,7 +210,7 @@ public function testBuild() {
       Link::createFromRoute('Forums', 'forum.index'),
       Link::createFromRoute('Something', 'forum.page', array('taxonomy_term' => 1)),
     );
-    $this->assertEquals($expected1, $breadcrumb_builder->build($route_match));
+    $this->assertEquals($expected1, $breadcrumb_builder->build($route_match)->getLinks());
 
     // Second test.
     $expected2 = array(
@@ -212,7 +219,7 @@ public function testBuild() {
       Link::createFromRoute('Something else', 'forum.page', array('taxonomy_term' => 2)),
       Link::createFromRoute('Something', 'forum.page', array('taxonomy_term' => 1)),
     );
-    $this->assertEquals($expected2, $breadcrumb_builder->build($route_match));
+    $this->assertEquals($expected2, $breadcrumb_builder->build($route_match)->getLinks());
   }
 
 }
diff --git a/core/modules/menu_ui/menu_ui.module b/core/modules/menu_ui/menu_ui.module
index 0c097ba..3872aa1 100644
--- a/core/modules/menu_ui/menu_ui.module
+++ b/core/modules/menu_ui/menu_ui.module
@@ -486,14 +486,14 @@ function menu_ui_preprocess_block(&$variables) {
 /**
  * Implements hook_system_breadcrumb_alter().
  */
-function menu_ui_system_breadcrumb_alter(array &$breadcrumb, RouteMatchInterface $route_match, array $context) {
+function menu_ui_system_breadcrumb_alter(&$breadcrumb, RouteMatchInterface $route_match, array $context) {
   // Custom breadcrumb behavior for editing menu links, we append a link to
   // the menu in which the link is found.
   if (($route_match->getRouteName() == 'menu_ui.link_edit') && $menu_link = $route_match->getParameter('menu_link_plugin')) {
     if (($menu_link instanceof MenuLinkInterface)) {
       // Add a link to the menu admin screen.
       $menu = Menu::load($menu_link->getMenuName());
-      $breadcrumb[] = Link::createFromRoute($menu->label(), 'entity.menu.edit_form', array('menu' => $menu->id()));
+      $breadcrumb->addLink(Link::createFromRoute($menu->label(), 'entity.menu.edit_form', array('menu' => $menu->id())));
     }
   }
 }
diff --git a/core/modules/page_cache/src/Tests/PageCacheTagsIntegrationTest.php b/core/modules/page_cache/src/Tests/PageCacheTagsIntegrationTest.php
index 95bd739..a7f9efe 100644
--- a/core/modules/page_cache/src/Tests/PageCacheTagsIntegrationTest.php
+++ b/core/modules/page_cache/src/Tests/PageCacheTagsIntegrationTest.php
@@ -77,6 +77,7 @@ function testPageCacheTags() {
       'route.menu_active_trails:tools',
       'theme',
       'timezone',
+      'url',
       'user.permissions',
       // The cache contexts associated with the (in)accessible menu links are
       // bubbled.
diff --git a/core/modules/system/src/PathBasedBreadcrumbBuilder.php b/core/modules/system/src/PathBasedBreadcrumbBuilder.php
index 45418a7b..30608a8 100644
--- a/core/modules/system/src/PathBasedBreadcrumbBuilder.php
+++ b/core/modules/system/src/PathBasedBreadcrumbBuilder.php
@@ -9,7 +9,9 @@
 
 use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Access\AccessManagerInterface;
+use Drupal\Core\Breadcrumb\Breadcrumb;
 use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Controller\TitleResolverInterface;
 use Drupal\Core\Link;
@@ -125,6 +127,7 @@ public function applies(RouteMatchInterface $route_match) {
    * {@inheritdoc}
    */
   public function build(RouteMatchInterface $route_match) {
+    $breadcrumb = new Breadcrumb();
     $links = array();
 
     // General path-based breadcrumbs. Use the actual request path, prior to
@@ -139,17 +142,21 @@ public function build(RouteMatchInterface $route_match) {
     // /user is just a redirect, so skip it.
     // @todo Find a better way to deal with /user.
     $exclude['user'] = TRUE;
+    // Because this breadcrumb builder is entirely path-based, vary by the 'url'
+    // cache context.
+    $breadcrumb->setCacheContexts(['url.path']);
     while (count($path_elements) > 1) {
       array_pop($path_elements);
       // Copy the path elements for up-casting.
       $route_request = $this->getRequestForPath(implode('/', $path_elements), $exclude);
       if ($route_request) {
         $route_match = RouteMatch::createFromRequest($route_request);
-        $access = $this->accessManager->check($route_match, $this->currentUser);
-        if ($access) {
+        $access = $this->accessManager->check($route_match, $this->currentUser, NULL, TRUE);
+        // The set of breadcrumb links depends on the access result, so merge
+        // the access result's cacheability metadata.
+        $breadcrumb = $breadcrumb->merge(CacheableMetadata::createFromObject($access));
+        if ($access->isAllowed()) {
           $title = $this->titleResolver->getTitle($route_request, $route_match->getRouteObject());
-        }
-        if ($access) {
           if (!isset($title)) {
             // Fallback to using the raw path component as the title if the
             // route is missing a _title or _title_callback attribute.
@@ -165,7 +172,8 @@ public function build(RouteMatchInterface $route_match) {
       // Add the Home link, except for the front page.
       $links[] = Link::createFromRoute($this->t('Home'), '<front>');
     }
-    return array_reverse($links);
+
+    return $breadcrumb->setLinks(array_reverse($links));
   }
 
   /**
diff --git a/core/modules/system/src/Plugin/Block/SystemBreadcrumbBlock.php b/core/modules/system/src/Plugin/Block/SystemBreadcrumbBlock.php
index c7629f0..8c0eb88 100644
--- a/core/modules/system/src/Plugin/Block/SystemBreadcrumbBlock.php
+++ b/core/modules/system/src/Plugin/Block/SystemBreadcrumbBlock.php
@@ -79,18 +79,12 @@ public function build() {
       // $breadcrumb is expected to be an array of rendered breadcrumb links.
       return array(
         '#theme' => 'breadcrumb',
-        '#links' => $breadcrumb,
+        '#links' => $breadcrumb->getLinks(),
+        '#cache' => [
+          'contexts' => $breadcrumb->getCacheContexts(),
+        ],
       );
     }
   }
 
-  /**
-   * {@inheritdoc}
-   *
-   * @todo Make cacheable in https://www.drupal.org/node/2483183
-   */
-  public function getCacheMaxAge() {
-    return 0;
-  }
-
 }
diff --git a/core/modules/system/tests/src/Unit/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php b/core/modules/system/tests/src/Unit/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php
index 68e1abf..26c4a86 100644
--- a/core/modules/system/tests/src/Unit/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php
+++ b/core/modules/system/tests/src/Unit/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php
@@ -8,11 +8,12 @@
 namespace Drupal\Tests\system\Unit\Breadcrumbs;
 
 use Drupal\Core\Link;
-use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Access\AccessResultAllowed;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\StringTranslation\TranslationInterface;
 use Drupal\Core\Url;
 use Drupal\Core\Utility\LinkGeneratorInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\system\PathBasedBreadcrumbBuilder;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
@@ -130,7 +131,7 @@ public function testBuildOnFrontpage() {
       ->will($this->returnValue('/'));
 
     $links = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
-    $this->assertEquals(array(), $links);
+    $this->assertEquals([], $links->getLinks());
   }
 
   /**
@@ -144,7 +145,7 @@ public function testBuildWithOnePathElement() {
       ->will($this->returnValue('/example'));
 
     $links = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
-    $this->assertEquals(array(0 => new Link('Home', new Url('<front>'))), $links);
+    $this->assertEquals([0 => new Link('Home', new Url('<front>'))], $links->getLinks());
   }
 
   /**
@@ -176,7 +177,7 @@ public function testBuildWithTwoPathElements() {
     $this->setupAccessManagerToAllow();
 
     $links = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
-    $this->assertEquals(array(0 => new Link('Home', new Url('<front>')), 1 => new Link('Example', new Url('example'))), $links);
+    $this->assertEquals([0 => new Link('Home', new Url('<front>')), 1 => new Link('Example', new Url('example'))], $links->getLinks());
   }
 
   /**
@@ -216,11 +217,11 @@ public function testBuildWithThreePathElements() {
     $this->setupAccessManagerToAllow();
 
     $links = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
-    $this->assertEquals(array(
+    $this->assertEquals([
       new Link('Home', new Url('<front>')),
       new Link('Example', new Url('example')),
       new Link('Bar', new Url('example_bar')),
-    ), $links);
+    ], $links->getLinks());
   }
 
   /**
@@ -244,7 +245,7 @@ public function testBuildWithException($exception_class, $exception_argument) {
     $links = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
 
     // No path matched, though at least the frontpage is displayed.
-    $this->assertEquals(array(0 => new Link('Home', new Url('<front>'))), $links);
+    $this->assertEquals([0 => new Link('Home', new Url('<front>'))], $links->getLinks());
   }
 
   /**
@@ -285,7 +286,7 @@ public function testBuildWithNonProcessedPath() {
     $links = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
 
     // No path matched, though at least the frontpage is displayed.
-    $this->assertEquals(array(0 => new Link('Home', new Url('<front>'))), $links);
+    $this->assertEquals([0 => new Link('Home', new Url('<front>'))], $links->getLinks());
   }
 
   /**
@@ -330,7 +331,7 @@ public function testBuildWithUserPath() {
       ->will($this->returnValue('Admin'));
 
     $links = $this->builder->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
-    $this->assertEquals(array(0 => new Link('Home', new Url('<front>')), 1 => new Link('Admin', new Url('user_page'))), $links);
+    $this->assertEquals([0 => new Link('Home', new Url('<front>')), 1 => new Link('Admin', new Url('user_page'))], $links->getLinks());
   }
 
   /**
@@ -339,7 +340,7 @@ public function testBuildWithUserPath() {
   public function setupAccessManagerToAllow() {
     $this->accessManager->expects($this->any())
       ->method('check')
-      ->willReturn(TRUE);
+      ->willReturn(new AccessResultAllowed());
   }
 
   protected function setupStubPathProcessor() {
diff --git a/core/modules/taxonomy/src/TermBreadcrumbBuilder.php b/core/modules/taxonomy/src/TermBreadcrumbBuilder.php
index 7c4cc89..818f51d 100644
--- a/core/modules/taxonomy/src/TermBreadcrumbBuilder.php
+++ b/core/modules/taxonomy/src/TermBreadcrumbBuilder.php
@@ -8,6 +8,7 @@
 namespace Drupal\taxonomy;
 
 use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
+use Drupal\Core\Breadcrumb\Breadcrumb;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Link;
 use Drupal\Core\Routing\RouteMatchInterface;
@@ -67,9 +68,14 @@ public function build(RouteMatchInterface $route_match) {
       $breadcrumb[] = Link::createFromRoute($term->getName(), 'entity.taxonomy_term.canonical', array('taxonomy_term' => $term->id()));
     }
     $breadcrumb[] = Link::createFromRoute($this->t('Home'), '<front>');
-    $breadcrumb = array_reverse($breadcrumb);
+    $links = array_reverse($breadcrumb);
 
-    return $breadcrumb;
+    $breadcrumb = new Breadcrumb();
+    // This breadcrumb builder is based on a route parameter, and hence it
+    // depends on the 'route' cache context.
+    $breadcrumb->setCacheContexts(['route']);
+
+    return $breadcrumb->setLinks($links);
   }
 
 }
diff --git a/core/tests/Drupal/Tests/Core/Breadcrumb/BreadcrumbManagerTest.php b/core/tests/Drupal/Tests/Core/Breadcrumb/BreadcrumbManagerTest.php
index a2cbbf0..f2f55ca 100644
--- a/core/tests/Drupal/Tests/Core/Breadcrumb/BreadcrumbManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Breadcrumb/BreadcrumbManagerTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\Core\Breadcrumb;
 
+use Drupal\Core\Breadcrumb\Breadcrumb;
 use Drupal\Core\Breadcrumb\BreadcrumbManager;
 use Drupal\Tests\UnitTestCase;
 
@@ -17,6 +18,13 @@
 class BreadcrumbManagerTest extends UnitTestCase {
 
   /**
+   * The breadcrumb object.
+   *
+   * @var \Drupal\Core\Breadcrumb\Breadcrumb
+   */
+  protected $breadcrumb;
+
+  /**
    * The tested breadcrumb manager.
    *
    * @var \Drupal\Core\Breadcrumb\BreadcrumbManager
@@ -36,6 +44,7 @@ class BreadcrumbManagerTest extends UnitTestCase {
   protected function setUp() {
     $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
     $this->breadcrumbManager = new BreadcrumbManager($this->moduleHandler);
+    $this->breadcrumb = new Breadcrumb();
   }
 
   /**
@@ -43,7 +52,7 @@ protected function setUp() {
    */
   public function testBuildWithoutBuilder() {
     $result = $this->breadcrumbManager->build($this->getMock('Drupal\Core\Routing\RouteMatchInterface'));
-    $this->assertEquals(array(), $result);
+    $this->assertEquals(array(), $result->getLinks());
   }
 
   /**
@@ -51,7 +60,8 @@ public function testBuildWithoutBuilder() {
    */
   public function testBuildWithSingleBuilder() {
     $builder = $this->getMock('Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface');
-    $breadcrumb = array('<a href="/example">Test</a>');
+    $links = array('<a href="/example">Test</a>');
+    $this->breadcrumb->setLinks($links);
 
     $builder->expects($this->once())
       ->method('applies')
@@ -59,17 +69,17 @@ public function testBuildWithSingleBuilder() {
 
     $builder->expects($this->once())
       ->method('build')
-      ->will($this->returnValue($breadcrumb));
+      ->willReturn($this->breadcrumb);
 
     $route_match = $this->getMock('Drupal\Core\Routing\RouteMatchInterface');
     $this->moduleHandler->expects($this->once())
       ->method('alter')
-      ->with('system_breadcrumb', $breadcrumb, $route_match, array('builder' => $builder));
+      ->with('system_breadcrumb', $links, $route_match, array('builder' => $builder));
 
     $this->breadcrumbManager->addBuilder($builder, 0);
 
     $result = $this->breadcrumbManager->build($route_match);
-    $this->assertEquals($breadcrumb, $result);
+    $this->assertEquals($links, $result->getLinks());
   }
 
   /**
@@ -83,25 +93,26 @@ public function testBuildWithMultipleApplyingBuilders() {
       ->method('build');
 
     $builder2 = $this->getMock('Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface');
-    $breadcrumb2 = array('<a href="/example2">Test2</a>');
+    $links2 = array('<a href="/example2">Test2</a>');
+    $this->breadcrumb->setLinks($links2);
     $builder2->expects($this->once())
       ->method('applies')
       ->will($this->returnValue(TRUE));
     $builder2->expects($this->once())
       ->method('build')
-      ->will($this->returnValue($breadcrumb2));
+      ->willReturn($this->breadcrumb);
 
     $route_match = $this->getMock('Drupal\Core\Routing\RouteMatchInterface');
 
     $this->moduleHandler->expects($this->once())
       ->method('alter')
-      ->with('system_breadcrumb', $breadcrumb2, $route_match, array('builder' => $builder2));
+      ->with('system_breadcrumb', $links2, $route_match, array('builder' => $builder2));
 
     $this->breadcrumbManager->addBuilder($builder1, 0);
     $this->breadcrumbManager->addBuilder($builder2, 10);
 
     $result = $this->breadcrumbManager->build($route_match);
-    $this->assertEquals($breadcrumb2, $result);
+    $this->assertEquals($links2, $result->getLinks());
   }
 
   /**
@@ -116,25 +127,26 @@ public function testBuildWithOneNotApplyingBuilders() {
       ->method('build');
 
     $builder2 = $this->getMock('Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface');
-    $breadcrumb2 = array('<a href="/example2">Test2</a>');
+    $links2 = array('<a href="/example2">Test2</a>');
+    $this->breadcrumb->setLinks($links2);
     $builder2->expects($this->once())
       ->method('applies')
       ->will($this->returnValue(TRUE));
     $builder2->expects($this->once())
       ->method('build')
-      ->will($this->returnValue($breadcrumb2));
+      ->willReturn($this->breadcrumb);
 
     $route_match = $this->getMock('Drupal\Core\Routing\RouteMatchInterface');
 
     $this->moduleHandler->expects($this->once())
       ->method('alter')
-      ->with('system_breadcrumb', $breadcrumb2, $route_match, array('builder' => $builder2));
+      ->with('system_breadcrumb', $links2, $route_match, array('builder' => $builder2));
 
     $this->breadcrumbManager->addBuilder($builder1, 10);
     $this->breadcrumbManager->addBuilder($builder2, 0);
 
     $result = $this->breadcrumbManager->build($route_match);
-    $this->assertEquals($breadcrumb2, $result);
+    $this->assertEquals($links2, $result->getLinks());
   }
 
   /**
