diff --git a/core/core.services.yml b/core/core.services.yml
index a4327bda9b..9944928dff 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1164,6 +1164,10 @@ services:
     class: Drupal\Core\Entity\EntityAccessCheck
     tags:
       - { name: access_check, applies_to: _entity_access }
+  access_check.entity_bundles:
+    class: Drupal\Core\Entity\EntityBundleAccessCheck
+    tags:
+      - { name: access_check, applies_to: _entity_bundles }
   access_check.entity_create:
     class: Drupal\Core\Entity\EntityCreateAccessCheck
     arguments: ['@entity_type.manager']
diff --git a/core/lib/Drupal/Core/Entity/EntityBundleAccessCheck.php b/core/lib/Drupal/Core/Entity/EntityBundleAccessCheck.php
new file mode 100644
index 0000000000..17d197b22a
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityBundleAccessCheck.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\Core\Entity;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Provides an access checker for the _entity_bundles route requirement.
+ */
+class EntityBundleAccessCheck implements AccessInterface {
+
+  /**
+   * Checks access based on the _entity_bundles route requirement.
+   *
+   * @code
+   * example.route:
+   *   path: foo/{example_entity_type}/{other_parameter}
+   *   requirements:
+   *     _entity_bundles: 'example_entity_type:example_bundle|other_example_bundle'
+   * @endcode
+   *
+   * @param \Symfony\Component\Routing\Route $route
+   *   The route to check against.
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The parametrized route
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The currently logged in account.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The access result.
+   */
+  public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) {
+    if ($route->hasRequirement('_entity_bundles')) {
+      list($entity_type, $bundle_definition) = explode(':', $route->getRequirement('_entity_bundles'));
+      $bundles = explode('|', $bundle_definition);
+      $parameters = $route_match->getParameters();
+      if ($parameters->has($entity_type)) {
+        $entity = $parameters->get($entity_type);
+        if ($entity instanceof EntityInterface && in_array($entity->bundle(), $bundles, TRUE)) {
+          return AccessResult::allowed()->addCacheableDependency($entity);
+        }
+      }
+    }
+    return AccessResult::forbidden('The entity bundle does not match the route _entity_bundles requirement.');
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityBundleAccessCheckTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityBundleAccessCheckTest.php
new file mode 100644
index 0000000000..729f3e4ddb
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityBundleAccessCheckTest.php
@@ -0,0 +1,94 @@
+<?php
+
+namespace Drupal\Tests\Core\Entity;
+
+use Drupal\Core\Cache\Context\CacheContextsManager;
+use Drupal\Core\DependencyInjection\Container;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\node\NodeInterface;
+use Symfony\Component\HttpFoundation\ParameterBag;
+use Symfony\Component\Routing\Route;
+use Drupal\Core\Access\AccessibleInterface;
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Entity\EntityBundleAccessCheck;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Unit test of entity access checking system.
+ *
+ * @coversDefaultClass \Drupal\Core\Entity\EntityBundleAccessCheck
+ *
+ * @group Access
+ * @group Entity
+ */
+class EntityBundleAccessCheckTest extends UnitTestCase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    $cache_contexts_manager = $this->prophesize(CacheContextsManager::class)->reveal();
+    $container = new Container();
+    $container->set('cache_contexts_manager', $cache_contexts_manager);
+    \Drupal::setContainer($container);
+  }
+
+  /**
+   * Data provider.
+   */
+  function getBundleAndAccessResult() {
+    return [
+      [
+        'article',
+        'node:article',
+        AccessResult::allowed(),
+      ],
+      [
+        'page',
+        'node:article',
+        AccessResult::forbidden('The entity bundle does not match the route _entity_bundles requirement.'),
+      ],
+      [
+        'page',
+        'node:article|page',
+        AccessResult::allowed(),
+      ],
+      [
+        'article',
+        'node:article|page',
+        AccessResult::allowed(),
+      ],
+    ];
+  }
+
+  /**
+   * @covers ::access
+   *
+   * @dataProvider getBundleAndAccessResult
+   */
+  public function testRouteAccess($bundle, $access_requirement, $access_result) {
+    $route = new Route('/foo/{node}', [], ['_entity_bundles' => $access_requirement], ['parameters' => ['node' => ['type' => 'entity:node']]]);
+    /** @var \Drupal\Core\Session\AccountInterface $account */
+    $account = $this->prophesize(AccountInterface::class)->reveal();
+
+    /** @var \Drupal\node\NodeInterface|\Prophecy\Prophecy\ObjectProphecy $node */
+    $node = $this->prophesize(NodeInterface::class);
+    $node->bundle()->willReturn($bundle);
+    $node->getCacheContexts()->willReturn([]);
+    $node->getCacheTags()->willReturn([]);
+    $node->getCacheMaxAge()->willReturn(-1);
+    $node = $node->reveal();
+
+    /** @var \Drupal\Core\Routing\RouteMatchInterface|\Prophecy\Prophecy\ObjectProphecy $route_match */
+    $route_match = $this->prophesize(RouteMatchInterface::class);
+    $route_match->getRawParameters()->willReturn(new ParameterBag(['node' => 1]));
+    $route_match->getParameters()->willReturn(new ParameterBag(['node' => $node]));
+    $route_match = $route_match->reveal();
+
+    $access_check = new EntityBundleAccessCheck();
+    $this->assertEquals($access_result, $access_check->access($route, $route_match, $account));
+  }
+
+}
