diff --git a/core/lib/Drupal/Core/Entity/Controller/EntityController.php b/core/lib/Drupal/Core/Entity/Controller/EntityController.php
index 0af9638..a3a4fe4 100644
--- a/core/lib/Drupal/Core/Entity/Controller/EntityController.php
+++ b/core/lib/Drupal/Core/Entity/Controller/EntityController.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\Core\Entity\Controller;
 
-use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityRepositoryInterface;
+use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
@@ -19,6 +21,8 @@
  * Provides generic entity title callbacks for use in routing.
  *
  * It provides:
+ * - An add title callback for entities without bundles.
+ * - An add title callback for entities with bundles.
  * - A view title callback.
  * - An edit title callback.
  * - A delete title callback.
@@ -30,20 +34,40 @@ class EntityController implements ContainerInjectionInterface {
   /**
    * The entity manager.
    *
-   * @var \Drupal\Core\Entity\EntityManagerInterface
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
    */
-  protected $entityManager;
+  protected $entityTypeManager;
+
+  /**
+   * The entity type bundle info.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
+   */
+  protected $entityTypeBundleInfo;
+
+  /**
+   * The entity repository.
+   *
+   * @var \Drupal\Core\Entity\EntityRepositoryInterface
+   */
+  protected $entityRepository;
 
   /**
    * Constructs a new EntityController.
    *
-   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
-   *   The entity manager.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
+   *   The entity type bundle info.
+   * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
+   *   The entity repository.
    * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
    *   The string translation.
    */
-  public function __construct(EntityManagerInterface $entity_manager, TranslationInterface $string_translation) {
-    $this->entityManager = $entity_manager;
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityRepositoryInterface $entity_repository, TranslationInterface $string_translation) {
+    $this->entityTypeManager = $entity_type_manager;
+    $this->entityTypeBundleInfo = $entity_type_bundle_info;
+    $this->entityRepository = $entity_repository;
     $this->stringTranslation = $string_translation;
   }
 
@@ -52,12 +76,53 @@ public function __construct(EntityManagerInterface $entity_manager, TranslationI
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('entity.manager'),
+      $container->get('entity_type.manager'),
+      $container->get('entity_type.bundle.info'),
+      $container->get('entity.repository'),
       $container->get('string_translation')
     );
   }
 
   /**
+   * Provides a generic add title callback for entities without bundles.
+   *
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The route match.
+   * @param string $entity_type_id
+   *   The entity type ID.
+   *
+   * @return string
+   *    The title for the entity add page.
+   */
+  public function addTitle(RouteMatchInterface $route_match, $entity_type_id) {
+    $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
+    return $this->t('Add @entity-type', ['@entity-type' => $entity_type->getLowercaseLabel()]);
+  }
+
+  /**
+   * Provides a generic add title callback for entities with bundles.
+   *
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The route match.
+   * @param string $entity_type_id
+   *   The entity type ID.
+   * @param string $bundle_parameter
+   *   The name of the route parameter that holds the bundle.
+   *
+   * @return string
+   *    The title for the entity add page, if the bundle was found.
+   */
+  public function addBundleTitle(RouteMatchInterface $route_match, $entity_type_id, $bundle_parameter) {
+    $bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type_id);
+    // If entity has bundle entities, the parameter might have been upcasted so
+    // fetch the raw parameter.
+    $bundle = $route_match->getRawParameter($bundle_parameter);
+    if (isset($bundles[$bundle])) {
+      return $this->t('Add @bundle', ['@bundle' => $bundles[$bundle]['label']]);
+    }
+  }
+
+  /**
    * Provides a generic title callback for a single entity.
    *
    * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
@@ -65,8 +130,8 @@ public static function create(ContainerInterface $container) {
    * @param \Drupal\Core\Entity\EntityInterface $_entity
    *   (optional) An entity, passed in directly from the request attributes.
    *
-   * @return string
-   *   The title for the entity view page.
+   * @return string|null
+   *   The title for the entity view page, if an entity was found.
    */
   public function title(RouteMatchInterface $route_match, EntityInterface $_entity = NULL) {
     if ($entity = $this->doGetEntity($route_match, $_entity)) {
@@ -82,8 +147,8 @@ public function title(RouteMatchInterface $route_match, EntityInterface $_entity
    * @param \Drupal\Core\Entity\EntityInterface $_entity
    *   (optional) An entity, passed in directly from the request attributes.
    *
-   * @return string
-   *   The title for the entity edit page.
+   * @return string|null
+   *   The title for the entity edit page, if an entity was found.
    */
   public function editTitle(RouteMatchInterface $route_match, EntityInterface $_entity = NULL) {
     if ($entity = $this->doGetEntity($route_match, $_entity)) {
@@ -101,7 +166,7 @@ public function editTitle(RouteMatchInterface $route_match, EntityInterface $_en
    *   set in \Drupal\Core\Entity\Enhancer\EntityRouteEnhancer.
    *
    * @return string
-   *   The title for the delete entity page.
+   *   The title for the entity delete page.
    */
   public function deleteTitle(RouteMatchInterface $route_match, EntityInterface $_entity = NULL) {
     if ($entity = $this->doGetEntity($route_match, $_entity)) {
@@ -135,8 +200,8 @@ protected function doGetEntity(RouteMatchInterface $route_match, EntityInterface
         }
       }
     }
-    if ($entity) {
-      return $this->entityManager->getTranslationFromContext($entity);
+    if (isset($entity)) {
+      return $this->entityRepository->getTranslationFromContext($entity);
     }
   }
 
diff --git a/core/lib/Drupal/Core/Entity/EntityForm.php b/core/lib/Drupal/Core/Entity/EntityForm.php
index 8d91948..f14f4ff 100644
--- a/core/lib/Drupal/Core/Entity/EntityForm.php
+++ b/core/lib/Drupal/Core/Entity/EntityForm.php
@@ -358,7 +358,20 @@ public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entit
       $entity = $route_match->getParameter($entity_type_id);
     }
     else {
-      $entity = $this->entityManager->getStorage($entity_type_id)->create([]);
+      $values = [];
+
+      // If the entity has bundles, fetch it from the route match.
+      $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
+      if ($bundle_key = $entity_type->getKey('bundle')) {
+        if (($bundle_entity_type_id = $entity_type->getBundleEntityType()) && $route_match->getRawParameter($bundle_entity_type_id)) {
+          $values[$bundle_key] = $route_match->getParameter($bundle_entity_type_id)->id();
+        }
+        if ($route_match->getRawParameter($bundle_key)) {
+          $values[$bundle_key] = $route_match->getParameter($bundle_key);
+        }
+      }
+
+      $entity = $this->entityTypeManager->getStorage($entity_type_id)->create($values);
     }
 
     return $entity;
diff --git a/core/lib/Drupal/Core/Entity/Routing/AdminHtmlRouteProvider.php b/core/lib/Drupal/Core/Entity/Routing/AdminHtmlRouteProvider.php
index 39c1c6e..4cb3527 100644
--- a/core/lib/Drupal/Core/Entity/Routing/AdminHtmlRouteProvider.php
+++ b/core/lib/Drupal/Core/Entity/Routing/AdminHtmlRouteProvider.php
@@ -24,6 +24,16 @@ class AdminHtmlRouteProvider extends DefaultHtmlRouteProvider {
   /**
    * {@inheritdoc}
    */
+  protected function getAddFormRoute(EntityTypeInterface $entity_type) {
+    if ($route = parent::getAddFormRoute($entity_type)) {
+      $route->setOption('_admin_route', TRUE);
+      return $route;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   protected function getEditFormRoute(EntityTypeInterface $entity_type) {
     if ($route = parent::getEditFormRoute($entity_type)) {
       $route->setOption('_admin_route', TRUE);
diff --git a/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
index 6a8ed39..f07e5d3 100644
--- a/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
+++ b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
@@ -7,9 +7,12 @@
 
 namespace Drupal\Core\Entity\Routing;
 
+use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
+use Drupal\Core\Entity\Controller\EntityController;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\Core\Entity\EntityHandlerInterface;
-use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\Routing\Route;
@@ -31,20 +34,30 @@
 class DefaultHtmlRouteProvider implements EntityRouteProviderInterface, EntityHandlerInterface {
 
   /**
-   * The entity manager.
+   * The entity type manager.
    *
    * @var \Drupal\Core\Entity\EntityManagerInterface
    */
-  protected $entityManager;
+  protected $entityTypeManager;
+
+  /**
+   * The entity field manager.
+   *
+   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+   */
+  protected $entityFieldManager;
 
   /**
    * Constructs a new DefaultHtmlRouteProvider.
    *
-   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
-   *   The entity manager.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
+   *   The entity field manager.
    */
-  public function  __construct(EntityManagerInterface $entity_manager) {
-    $this->entityManager = $entity_manager;
+  public function  __construct(EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+    $this->entityFieldManager = $entity_field_manager;
   }
 
   /**
@@ -52,7 +65,8 @@ public function  __construct(EntityManagerInterface $entity_manager) {
    */
   public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
     return new static(
-      $container->get('entity.manager')
+      $container->get('entity_type.manager'),
+      $container->get('entity_field.manager')
     );
   }
 
@@ -64,14 +78,18 @@ public function getRoutes(EntityTypeInterface $entity_type) {
 
     $entity_type_id = $entity_type->id();
 
-    if ($edit_route = $this->getEditFormRoute($entity_type)) {
-      $collection->add("entity.{$entity_type_id}.edit_form", $edit_route);
+    if ($add_route = $this->getAddFormRoute($entity_type)) {
+      $collection->add("entity.{$entity_type_id}.add_form", $add_route);
     }
 
     if ($canonical_route = $this->getCanonicalRoute($entity_type)) {
       $collection->add("entity.{$entity_type_id}.canonical", $canonical_route);
     }
 
+    if ($edit_route = $this->getEditFormRoute($entity_type)) {
+      $collection->add("entity.{$entity_type_id}.edit_form", $edit_route);
+    }
+
     if ($delete_route = $this->getDeleteFormRoute($entity_type)) {
       $collection->add("entity.{$entity_type_id}.delete_form", $delete_route);
     }
@@ -80,6 +98,79 @@ public function getRoutes(EntityTypeInterface $entity_type) {
   }
 
   /**
+   * Gets the add-form route.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   *
+   * @return \Symfony\Component\Routing\Route|null
+   *   The generated route, if available.
+   */
+  protected function getAddFormRoute(EntityTypeInterface $entity_type) {
+    if ($entity_type->hasLinkTemplate('add-form')) {
+      $entity_type_id = $entity_type->id();
+      $route = new Route($entity_type->getLinkTemplate('add-form'));
+      // Use the add form handler, if available, otherwise default.
+      $operation = 'default';
+      if ($entity_type->getFormClass('add')) {
+        $operation = 'add';
+      }
+      $route->setDefaults([
+        '_entity_form' => "{$entity_type_id}.{$operation}",
+        'entity_type_id' => $entity_type_id,
+      ]);
+
+      // If the entity has bundles, we can provide a bundle-specific title
+      // and access requirements.
+      if ($bundle_key = $entity_type->getKey('bundle')) {
+        $route->setDefault('_title_callback', EntityController::class . '::addBundleTitle');
+        // If the bundles are entities themselves, we can add parameter
+        // information to the route options.
+        if ($bundle_entity_type_id = $entity_type->getBundleEntityType()) {
+          $bundle_entity_type = $this->entityTypeManager->getDefinition($bundle_entity_type_id);
+
+          $route
+            // The title callback uses the value of the bundle parameter to
+            // fetch the respective bundle at runtime.
+            ->setDefault('bundle_parameter', $bundle_entity_type_id)
+            ->setRequirement('_entity_create_access', "{$entity_type_id}:{$bundle_entity_type_id}");
+
+          // Entity types with serial IDs can specify this in their route
+          // requirements, improving the matching process.
+          if ($this->getEntityTypeIdKeyType($bundle_entity_type) === 'integer') {
+            $route->setRequirement($entity_type_id, '\d+');
+          }
+
+          $bundle_entity_parameter = ['type' => 'entity:' . $bundle_entity_type_id];
+          if ($bundle_entity_type instanceof ConfigEntityTypeInterface) {
+            // The add page might be displayed on an admin path. Even then, we
+            // need to load configuration overrides so that, for example, the
+            // bundle label gets translated correctly.
+            // @see \Drupal\Core\ParamConverter\AdminPathConfigEntityConverter
+            $bundle_entity_parameter['with_config_overrides'] = TRUE;
+          }
+          $route->setOption('parameters', [$bundle_entity_type_id => $bundle_entity_parameter]);
+        }
+        else {
+          // If the bundles are not entities, the bundle key is used as the
+          // route parameter name directly.
+          $route
+            // See above.
+            ->setDefault('bundle_parameter', $bundle_key)
+            ->setRequirement('_entity_create_access', "{$entity_type_id}:{$bundle_key}");
+        }
+      }
+      else {
+        $route
+          ->setDefault('_title_callback', EntityController::class . '::addTitle')
+          ->setRequirement('_entity_create_access', $entity_type_id);
+      }
+
+      return $route;
+    }
+  }
+
+  /**
    * Gets the canonical route.
    *
    * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
@@ -195,7 +286,7 @@ protected function getEntityTypeIdKeyType(EntityTypeInterface $entity_type) {
       return NULL;
     }
 
-    $field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type->id());
+    $field_storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions($entity_type->id());
     return $field_storage_definitions[$entity_type->getKey('id')]->getType();
   }
 
diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module
index 59e44b6..c84d2de 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.module
+++ b/core/modules/system/tests/modules/entity_test/entity_test.module
@@ -67,6 +67,8 @@ function entity_test_entity_types($filter = NULL) {
   }
   if ($filter === ENTITY_TEST_TYPES_ROUTING) {
     $types[] = 'entity_test_base_field_display';
+    $types[] = 'entity_test_string_id';
+    $types[] = 'entity_test_no_id';
   }
   $types[] = 'entity_test_mulrev';
   $types[] = 'entity_test_mulrev_changed';
diff --git a/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php b/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php
index b44c7eb..51cc8bd 100644
--- a/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php
+++ b/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php
@@ -45,24 +45,6 @@ public static function create(ContainerInterface $container) {
   }
 
   /**
-   * Displays the 'Add new entity_test' form.
-   *
-   * @param string $entity_type_id
-   *   Name of the entity type for which a create form should be displayed.
-   *
-   * @return array
-   *   The processed form for a new entity_test.
-   *
-   * @see \Drupal\entity_test\Routing\EntityTestRoutes::routes()
-   */
-  public function testAdd($entity_type_id) {
-    $entity = entity_create($entity_type_id, array());
-    $form = $this->entityFormBuilder()->getForm($entity);
-    $form['#title'] = $this->t('Create an @type', array('@type' => $entity_type_id));
-    return $form;
-  }
-
-  /**
    * Returns an empty page.
    *
    * @see \Drupal\entity_test\Routing\EntityTestRoutes::routes()
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
index 1197c2c..a41808b 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
@@ -47,6 +47,7 @@
  *   },
  *   links = {
  *     "canonical" = "/entity_test/{entity_test}",
+ *     "add-form" = "/entity_test/add",
  *     "edit-form" = "/entity_test/manage/{entity_test}/edit",
  *     "delete-form" = "/entity_test/delete/entity_test/{entity_test}",
  *   },
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestBaseFieldDisplay.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestBaseFieldDisplay.php
index d2f8378..8a80a11 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestBaseFieldDisplay.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestBaseFieldDisplay.php
@@ -37,6 +37,7 @@
  *   },
  *   links = {
  *     "canonical" = "/entity_test_base_field_display/{entity_test_base_field_display}/edit",
+ *     "add-form" = "/entity_test_base_field_display/add",
  *     "edit-form" = "/entity_test_base_field_display/manage/{entity_test_base_field_display}",
  *     "delete-form" = "/entity_test/delete/entity_test_base_field_display/{entity_test_base_field_display}/edit",
  *   },
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php
index d1908d3..af6a5da 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php
@@ -38,6 +38,7 @@
  *     "langcode" = "langcode",
  *   },
  *   links = {
+ *      "add-form" = "/entity_test_mul/add",
  *     "canonical" = "/entity_test_mul/manage/{entity_test_mul}",
  *     "edit-form" = "/entity_test_mul/manage/{entity_test_mul}/edit",
  *     "delete-form" = "/entity_test/delete/entity_test_mul/{entity_test_mul}",
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php
index 055e290..887535a 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php
@@ -42,6 +42,7 @@
  *     "langcode" = "langcode"
  *   },
  *   links = {
+ *     "add-form" = "/entity_test_mul_changed/add",
  *     "canonical" = "/entity_test_mul_changed/manage/{entity_test_mul_changed}",
  *     "edit-form" = "/entity_test_mul_changed/manage/{entity_test_mul_changed}/edit",
  *     "delete-form" = "/entity_test/delete/entity_test_mul_changed/{entity_test_mul_changed}",
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulLangcodeKey.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulLangcodeKey.php
index 3f794e5..421e8c7 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulLangcodeKey.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulLangcodeKey.php
@@ -41,6 +41,7 @@
  *     "default_langcode" = "custom_default_langcode_key",
  *   },
  *   links = {
+ *     "add-form" = "/entity_test_mul_langcode_key/add",
  *     "canonical" = "/entity_test_mul_langcode_key/manage/{entity_test_mul_langcode_key}",
  *     "edit-form" = "/entity_test_mul_langcode_key/manage/{entity_test_mul_langcode_key}/edit",
  *     "delete-form" = "/entity_test/delete/entity_test_mul_langcode_key/{entity_test_mul_langcode_key}",
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
index 1eb7715..eaab7ac 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
@@ -41,6 +41,7 @@
  *     "langcode" = "langcode",
  *   },
  *   links = {
+ *     "add-form" = "/entity_test_mulrev/add",
  *     "canonical" = "/entity_test_mulrev/manage/{entity_test_mulrev}",
  *     "delete-form" = "/entity_test/delete/entity_test_mulrev/{entity_test_mulrev}",
  *     "edit-form" = "/entity_test_mulrev/manage/{entity_test_mulrev}/edit",
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php
index f2a9667..316e206 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php
@@ -43,6 +43,7 @@
  *     "langcode" = "langcode",
  *   },
  *   links = {
+ *     "add-form" = "/entity_test_mulrev_changed/add",
  *     "canonical" = "/entity_test_mulrev_changed/manage/{entity_test_mulrev_changed}",
  *     "delete-form" = "/entity_test/delete/entity_test_mulrev_changed/{entity_test_mulrev_changed}",
  *     "edit-form" = "/entity_test_mulrev_changed/manage/{entity_test_mulrev_changed}/edit",
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestNoId.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestNoId.php
index a29c1e2..bc0661e 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestNoId.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestNoId.php
@@ -19,7 +19,11 @@
  *   entity_keys = {
  *     "bundle" = "type",
  *   },
+ *   admin_permission = "administer entity_test content",
  *   field_ui_base_route = "entity.entity_test_no_id.admin_form",
+ *   links = {
+ *     "add-form" = "/entity_test_no_id/add",
+ *   },
  * )
  */
 class EntityTestNoId extends EntityTest {
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php
index 047b85d..7aed50a 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php
@@ -42,6 +42,7 @@
  *     "langcode" = "langcode",
  *   },
  *   links = {
+ *     "add-form" = "/entity_test_rev/add",
  *     "canonical" = "/entity_test_rev/manage/{entity_test_rev}",
  *     "delete-form" = "/entity_test/delete/entity_test_rev/{entity_test_rev}",
  *     "edit-form" = "/entity_test_rev/manage/{entity_test_rev}/edit",
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestStringId.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestStringId.php
index 1f53e04..41f793c 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestStringId.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestStringId.php
@@ -34,6 +34,7 @@
  *   },
  *   links = {
  *     "canonical" = "/entity_test_string_id/manage/{entity_test_string_id}",
+ *     "add-form" = "/entity_test_string_id/add",
  *     "edit-form" = "/entity_test_string_id/manage/{entity_test_string_id}",
  *   },
  *   field_ui_base_route = "entity.entity_test_string_id.admin_form",
diff --git a/core/modules/system/tests/modules/entity_test/src/Routing/EntityTestRoutes.php b/core/modules/system/tests/modules/entity_test/src/Routing/EntityTestRoutes.php
index 8992550..b607f9f 100644
--- a/core/modules/system/tests/modules/entity_test/src/Routing/EntityTestRoutes.php
+++ b/core/modules/system/tests/modules/entity_test/src/Routing/EntityTestRoutes.php
@@ -22,17 +22,9 @@ class EntityTestRoutes {
    */
   public function routes() {
     $types = entity_test_entity_types(ENTITY_TEST_TYPES_ROUTING);
-    $types[] = 'entity_test_string_id';
-    $types[] = 'entity_test_no_id';
 
     $routes = array();
     foreach ($types as $entity_type_id) {
-      $routes["entity.$entity_type_id.add_form"] = new Route(
-        "$entity_type_id/add",
-        array('_controller' => '\Drupal\entity_test\Controller\EntityTestController::testAdd', 'entity_type_id' => $entity_type_id),
-        array('_permission' => 'administer entity_test content')
-      );
-
       $routes["entity.$entity_type_id.admin_form"] = new Route(
         "$entity_type_id/structure/{bundle}",
         array('_controller' => '\Drupal\entity_test\Controller\EntityTestController::testAdmin'),
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityFormTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityFormTest.php
index 810bdd1..2794e5d 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityFormTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityFormTest.php
@@ -8,8 +8,14 @@
 namespace Drupal\Tests\Core\Entity;
 
 use Drupal\Core\Entity\EntityForm;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Entity\EntityType;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Form\FormState;
+use Drupal\Core\Routing\RouteMatch;
 use Drupal\Tests\UnitTestCase;
+use Symfony\Component\Routing\Route;
 
 /**
  * @coversDefaultClass \Drupal\Core\Entity\EntityForm
@@ -25,12 +31,21 @@ class EntityFormTest extends UnitTestCase {
   protected $entityForm;
 
   /**
+   * A fake entity type used in the test.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeInterface
+   */
+  protected $entityType;
+
+  /**
    * {@inheritdoc}
    */
   protected function setUp() {
     parent::setUp();
 
     $this->entityForm = new EntityForm();
+
+    $this->entityType = new EntityType(['id' => 'entity_test']);
   }
 
   /**
@@ -41,17 +56,13 @@ protected function setUp() {
    * @dataProvider providerTestFormIds
    */
   public function testFormId($expected, $definition) {
-    $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
-    $entity_type->expects($this->any())
-      ->method('hasKey')
-      ->with('bundle')
-      ->will($this->returnValue($definition['bundle']));
+    $this->entityType->set('entity_keys', ['bundle' => $definition['bundle']]);
 
     $entity = $this->getMockForAbstractClass('Drupal\Core\Entity\Entity', array(array(), $definition['entity_type']), '', TRUE, TRUE, TRUE, array('getEntityType', 'bundle'));
 
     $entity->expects($this->any())
       ->method('getEntityType')
-      ->will($this->returnValue($entity_type));
+      ->will($this->returnValue($this->entityType));
     $entity->expects($this->any())
       ->method('bundle')
       ->will($this->returnValue($definition['bundle']));
@@ -123,4 +134,115 @@ public function testCopyFormValuesToEntity() {
     $this->assertNull($result->get('key_controlled_by_plugin_collection'));
   }
 
+  /**
+   * Tests EntityForm::getEntityFromRouteMatch() for edit and delete forms.
+   *
+   * @covers ::getEntityFromRouteMatch
+   */
+  public function testGetEntityFromRouteMatchEditDelete() {
+    $entity = $this->prophesize(EntityInterface::class)->reveal();
+    $id = $this->entityType->id();
+    $route_match = new RouteMatch(
+      'test_route',
+      new Route('/entity-test/manage/{' . $id . '}/edit'),
+      [$id => $entity],
+      [$id => 1]
+    );
+    $actual = $this->entityForm->getEntityFromRouteMatch($route_match, $id);
+    $this->assertEquals($entity, $actual);
+  }
+
+  /**
+   * Tests EntityForm::getEntityFromRouteMatch() for add forms without a bundle.
+   *
+   * @covers ::getEntityFromRouteMatch
+   */
+  public function testGetEntityFromRouteMatchAdd() {
+    $entity = $this->prophesize(EntityInterface::class)->reveal();
+    $this->setUpStorage()->create([])->willReturn($entity);
+    $route_match = new RouteMatch('test_route',  new Route('/entity-test/add'));
+    $actual = $this->entityForm->getEntityFromRouteMatch($route_match, $this->entityType->id());
+    $this->assertEquals($entity, $actual);
+  }
+
+  /**
+   * Tests EntityForm::getEntityFromRouteMatch() with a static bundle.
+   *
+   * @covers ::getEntityFromRouteMatch
+   */
+  public function testGetEntityFromRouteMatchAddStatic() {
+    $entity = $this->prophesize(EntityInterface::class)->reveal();
+    $bundle_key = 'bundle';
+    $bundle = 'test_bundle';
+    $this->entityType->set('entity_keys', ['bundle' => $bundle_key]);
+    $storage = $this->setUpStorage();
+
+    // Test without a bundle parameter in the route.
+    $storage->create([])->willReturn($entity);
+    $route_match = new RouteMatch('test_route',  new Route('/entity-test/add'));
+    $actual = $this->entityForm->getEntityFromRouteMatch($route_match, $this->entityType->id());
+    $this->assertEquals($entity, $actual);
+
+    // Test with a static bundle parameter.
+    $storage->create([$bundle_key => 'test_bundle'])->willReturn($entity);
+    $route_match = new RouteMatch(
+      'test_route',
+      new Route('/entity-test/add/{' . $bundle_key . '}'),
+      [$bundle_key => $bundle],
+      [$bundle_key => $bundle]
+    );
+    $actual = $this->entityForm->getEntityFromRouteMatch($route_match, $this->entityType->id());
+    $this->assertEquals($entity, $actual);
+  }
+
+  /**
+   * Tests EntityForm::getEntityFromRouteMatch() with a config entity bundle.
+   *
+   * @covers ::getEntityFromRouteMatch
+   */
+  public function testGetEntityFromRouteMatchAddEntity() {
+    $entity = $this->prophesize(EntityInterface::class)->reveal();
+    $bundle_entity_type_id = 'entity_test_bundle';
+    $bundle = 'test_entity_bundle';
+    $this->entityType->set('bundle_entity_type', $bundle_entity_type_id);
+    $storage = $this->setUpStorage();
+
+    // Test without a bundle parameter in the route.
+    $storage->create([])->willReturn($entity);
+    $route_match = new RouteMatch('test_route',  new Route('/entity-test/add'));
+    $actual = $this->entityForm->getEntityFromRouteMatch($route_match, $this->entityType->id());
+    $this->assertEquals($entity, $actual);
+
+    // Test with an entity bundle parameter.
+    $storage->create(['bundle' => $bundle])->willReturn($entity);
+    $bundle_entity = $this->prophesize(EntityInterface::class);
+    $bundle_entity->id()->willReturn('test_entity_bundle');
+    $route_match = new RouteMatch(
+      'test_route',
+      new Route('/entity-test/add/{entity_test_bundle}'),
+      [$bundle_entity_type_id => $bundle_entity->reveal()],
+      [$bundle_entity_type_id => $bundle]
+    );
+    $actual = $this->entityForm->getEntityFromRouteMatch($route_match, $this->entityType->id());
+    $this->assertEquals($entity, $actual);
+  }
+
+  /**
+   * Sets up the storage accessed via the entity type manager in the form.
+   *
+   * @return \Prophecy\Prophecy\ObjectProphecy
+   *   The storage prophecy.
+   */
+  protected function setUpStorage() {
+    $storage = $this->prophesize(EntityStorageInterface::class);
+
+    $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
+    $entity_type_manager->getDefinition($this->entityType->id())->willReturn($this->entityType);
+    $entity_type_manager->getStorage($this->entityType->id())->willReturn($storage->reveal());
+
+    $this->entityForm->setEntityTypeManager($entity_type_manager->reveal());
+
+    return $storage;
+  }
+
 }
diff --git a/core/tests/Drupal/Tests/Core/Entity/Routing/DefaultHtmlRouteProviderTest.php b/core/tests/Drupal/Tests/Core/Entity/Routing/DefaultHtmlRouteProviderTest.php
index cdfd0f2..a1d04e1 100644
--- a/core/tests/Drupal/Tests/Core/Entity/Routing/DefaultHtmlRouteProviderTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/Routing/DefaultHtmlRouteProviderTest.php
@@ -7,13 +7,16 @@
 
 namespace Drupal\Tests\Core\Entity\Routing;
 
-use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\Tests\UnitTestCase;
 use Prophecy\Argument;
+use Prophecy\Prophecy\ObjectProphecy;
 use Symfony\Component\Routing\Route;
 
 /**
@@ -23,78 +26,161 @@
 class DefaultHtmlRouteProviderTest extends UnitTestCase {
 
   /**
-   * @covers ::getEntityTypeIdKeyType
+   * The entity type manager prophecy used in the test.
+   *
+   * @var \Prophecy\Prophecy\ProphecyInterface|\Drupal\Core\Entity\EntityTypeManagerInterface
    */
-  public function testGetEntityTypeIdKeyType() {
-    $entity_manager = $this->prophesize(EntityManagerInterface::class);
-    $route_provider = new TestDefaultHtmlRouteProvider($entity_manager->reveal());
+  protected $entityTypeManager;
 
-    $entity_type = $this->prophesize(EntityTypeInterface::class);
-    $entity_type->isSubclassOf(FieldableEntityInterface::class)->willReturn(TRUE);
-    $entity_type_id = 'the_entity_type_id';
-    $entity_type->id()->willReturn($entity_type_id);
-    $entity_type->getKey('id')->willReturn('id');
+  /**
+   * The entity field manager prophecy used in the test.
+   *
+   * @var \Prophecy\Prophecy\ProphecyInterface|\Drupal\Core\Entity\EntityFieldManagerInterface
+   */
+  protected $entityFieldManager;
 
-    $field_storage_definition = $this->prophesize(FieldStorageDefinitionInterface::class);
-    $field_storage_definition->getType()->willReturn('integer');
-    $entity_manager->getFieldStorageDefinitions($entity_type_id)->willReturn(['id' => $field_storage_definition]);
+  /**
+   * The HTML route provider used in the test.
+   *
+   * @var \Drupal\Tests\Core\Entity\Routing\TestDefaultHtmlRouteProvider
+   */
+  protected $routeProvider;
 
-    $type = $route_provider->getEntityTypeIdKeyType($entity_type->reveal());
-    $this->assertSame('integer', $type);
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
+    $this->entityFieldManager = $this->prophesize(EntityFieldManagerInterface::class);
+
+    $this->routeProvider = new TestDefaultHtmlRouteProvider($this->entityTypeManager->reveal(), $this->entityFieldManager->reveal());
   }
 
   /**
-   * @covers ::getEntityTypeIdKeyType
+   * @covers ::getAddFormRoute
+   * @dataProvider providerTestGetAddFormRoute
    */
-  public function testGetEntityTypeIdKeyTypeNotFieldable() {
-    $entity_manager = $this->prophesize(EntityManagerInterface::class);
-    $route_provider = new TestDefaultHtmlRouteProvider($entity_manager->reveal());
+  public function testGetAddFormRoute(Route $expected = NULL, EntityTypeInterface $entity_type, EntityTypeInterface $bundle_entity_type = NULL, FieldStorageDefinitionInterface $field_storage_definition = NULL) {
+    if ($bundle_entity_type) {
+      $this->entityTypeManager->getDefinition('the_bundle_entity_type_id')->willReturn($bundle_entity_type);
 
-    $entity_type = $this->prophesize(EntityTypeInterface::class);
-    $entity_type->isSubclassOf(FieldableEntityInterface::class)->willReturn(FALSE);
-    $entity_manager->getFieldStorageDefinitions(Argument::any())->shouldNotBeCalled();
+      if ($field_storage_definition) {
+        $this->entityFieldManager->getFieldStorageDefinitions('the_bundle_entity_type_id')
+          ->willReturn(['id' => $field_storage_definition]);
+      }
+    }
 
-    $type = $route_provider->getEntityTypeIdKeyType($entity_type->reveal());
-    $this->assertNull($type);
+    $route = $this->routeProvider->getAddFormRoute($entity_type);
+    $this->assertEquals($expected, $route);
+  }
+
+  public function providerTestGetAddFormRoute() {
+    $data = [];
+
+    $entity_type1 = $this->getEntityType();
+    $entity_type1->hasLinkTemplate('add-form')->willReturn(FALSE);
+    $data['no_add_form_link_template'] = [NULL, $entity_type1->reveal()];
+
+    $entity_type2 = $this->getEntityType();
+    $entity_type2->hasLinkTemplate('add-form')->willReturn(TRUE);
+    $entity_type2->id()->willReturn('the_entity_type_id');
+    $entity_type2->getLinkTemplate('add-form')->willReturn('/the/add/form/link/template');
+    $entity_type2->getFormClass('add')->willReturn(NULL);
+    $entity_type2->getKey('bundle')->willReturn(NULL);
+    $route = (new Route('/the/add/form/link/template'))
+      ->setDefaults([
+        '_entity_form' => 'the_entity_type_id.default',
+        'entity_type_id' => 'the_entity_type_id',
+        '_title_callback' => 'Drupal\Core\Entity\Controller\EntityController::addTitle',
+      ])
+      ->setRequirement('_entity_create_access', 'the_entity_type_id')
+    ;
+    $data['no_add_form_no_bundle'] = [clone $route, $entity_type2->reveal()];
+
+    $entity_type3 = $this->getEntityType($entity_type2);
+    $entity_type3->getFormClass('add')->willReturn('Drupal\Core\Entity\EntityForm');
+    $route->setDefault('_entity_form', 'the_entity_type_id.add');
+    $data['add_form_no_bundle'] = [clone $route, $entity_type3->reveal()];
+
+    $entity_type4 = $this->getEntityType($entity_type3);
+    $entity_type4->getKey('bundle')->willReturn('the_bundle_key');
+    $entity_type4->getBundleEntityType()->willReturn(NULL);
+    $route
+      ->setDefault('_title_callback', 'Drupal\Core\Entity\Controller\EntityController::addBundleTitle')
+      ->setDefault('bundle_parameter', 'the_bundle_key')
+      ->setRequirement('_entity_create_access', 'the_entity_type_id:the_bundle_key');
+    $data['add_form_bundle_static'] = [clone $route, $entity_type4->reveal()];
+
+    $entity_type5 = $this->getEntityType($entity_type4);
+    $entity_type5->getBundleEntityType()->willReturn('the_bundle_entity_type_id');
+    $bundle_entity_type = $this->getEntityType();
+    $bundle_entity_type->isSubclassOf(FieldableEntityInterface::class)->willReturn(FALSE);
+    $route
+      ->setDefault('bundle_parameter', 'the_bundle_entity_type_id')
+      ->setRequirement('_entity_create_access', 'the_entity_type_id:the_bundle_entity_type_id')
+      ->setOption('parameters', ['the_bundle_entity_type_id' => [
+        'type' => 'entity:the_bundle_entity_type_id',
+      ]]);
+    $data['add_form_bundle_entity_id_key_type_null'] = [clone $route, $entity_type5->reveal(), $bundle_entity_type->reveal()];
+
+    $entity_type6 = $this->getEntityType($entity_type5);
+    $bundle_entity_type = $this->getEntityType();
+    $bundle_entity_type->isSubclassOf(FieldableEntityInterface::class)->willReturn(TRUE);
+    $field_storage_definition = $this->prophesize(FieldStorageDefinitionInterface::class);
+    $field_storage_definition->getType()->willReturn('integer');
+    $route->setRequirement('the_entity_type_id', '\d+');
+    $data['add_form_bundle_entity_id_key_type_integer'] = [clone $route, $entity_type6->reveal(), $bundle_entity_type->reveal(), $field_storage_definition->reveal()];
+
+    $entity_type7 = $this->getEntityType($entity_type6);
+    $bundle_entity_type = $this->prophesize(ConfigEntityTypeInterface::class);
+    $bundle_entity_type->isSubclassOf(FieldableEntityInterface::class)->willReturn(FALSE);
+    $field_storage_definition = $this->prophesize(FieldStorageDefinitionInterface::class);
+    $route
+      // Unset the 'the_entity_type_id' requirement.
+      ->setRequirements(['_entity_create_access' => $route->getRequirement('_entity_create_access')])
+      ->setOption('parameters', ['the_bundle_entity_type_id' => [
+      'type' => 'entity:the_bundle_entity_type_id',
+      'with_config_overrides' => TRUE,
+    ]]);
+    $data['add_form_bundle_entity_id_key_type_integer'] = [clone $route, $entity_type7->reveal(), $bundle_entity_type->reveal(), $field_storage_definition->reveal()];
+
+    return $data;
   }
 
   /**
    * @covers ::getCanonicalRoute
    * @dataProvider providerTestGetCanonicalRoute
    */
-  public function testGetCanonicalRoute($entity_type_prophecy, $expected, $field_storage_definition = NULL) {
-    $entity_manager = $this->prophesize(EntityManagerInterface::class);
-    $route_provider = new TestDefaultHtmlRouteProvider($entity_manager->reveal());
-    $entity_type = $entity_type_prophecy->reveal();
-
+  public function testGetCanonicalRoute(Route $expected = NULL, EntityTypeInterface $entity_type, FieldStorageDefinitionInterface $field_storage_definition = NULL) {
     if ($field_storage_definition) {
-      $entity_manager->getFieldStorageDefinitions($entity_type->id())
+      $this->entityFieldManager->getFieldStorageDefinitions($entity_type->id())
         ->willReturn([$entity_type->getKey('id') => $field_storage_definition]);
     }
 
-    $route = $route_provider->getCanonicalRoute($entity_type);
+    $route = $this->routeProvider->getCanonicalRoute($entity_type);
     $this->assertEquals($expected, $route);
   }
 
   public function providerTestGetCanonicalRoute() {
     $data = [];
 
-    $entity_type1 = $this->prophesize(EntityTypeInterface::class);
+    $entity_type1 = $this->getEntityType();
     $entity_type1->hasLinkTemplate('canonical')->willReturn(FALSE);
-    $data['no_canonical_link_template'] = [$entity_type1, NULL];
+    $data['no_canonical_link_template'] = [NULL, $entity_type1->reveal()];
 
-    $entity_type2 = $this->prophesize(EntityTypeInterface::class);
+    $entity_type2 = $this->getEntityType();;
     $entity_type2->hasLinkTemplate('canonical')->willReturn(TRUE);
     $entity_type2->hasViewBuilderClass()->willReturn(FALSE);
-    $data['no_view_builder'] = [$entity_type2, NULL];
+    $data['no_view_builder'] = [NULL, $entity_type2->reveal()];
 
-    $entity_type3 = $this->prophesize(EntityTypeInterface::class);
-    $entity_type3->hasLinkTemplate('canonical')->willReturn(TRUE);
+    $entity_type3 = $this->getEntityType($entity_type2);
     $entity_type3->hasViewBuilderClass()->willReturn(TRUE);
     $entity_type3->id()->willReturn('the_entity_type_id');
     $entity_type3->getLinkTemplate('canonical')->willReturn('/the/canonical/link/template');
     $entity_type3->isSubclassOf(FieldableEntityInterface::class)->willReturn(FALSE);
-    $route3 = (new Route('/the/canonical/link/template'))
+    $route = (new Route('/the/canonical/link/template'))
       ->setDefaults([
         '_entity_view' => 'the_entity_type_id.full',
         '_title_callback' => '\Drupal\Core\Entity\Controller\EntityController::title',
@@ -109,38 +195,64 @@ public function providerTestGetCanonicalRoute() {
           ],
         ],
       ]);
-    $data['id_key_type_null'] = [$entity_type3, $route3];
+    $data['id_key_type_null'] = [clone $route, $entity_type3->reveal()];
 
-    $entity_type4 = $this->prophesize(EntityTypeInterface::class);
-    $entity_type4->hasLinkTemplate('canonical')->willReturn(TRUE);
-    $entity_type4->hasViewBuilderClass()->willReturn(TRUE);
-    $entity_type4->id()->willReturn('the_entity_type_id');
-    $entity_type4->getLinkTemplate('canonical')->willReturn('/the/canonical/link/template');
+    $entity_type4 = $this->getEntityType($entity_type3);
     $entity_type4->isSubclassOf(FieldableEntityInterface::class)->willReturn(TRUE);
     $entity_type4->getKey('id')->willReturn('id');
-    $route4 = (new Route('/the/canonical/link/template'))
-      ->setDefaults([
-        '_entity_view' => 'the_entity_type_id.full',
-        '_title_callback' => '\Drupal\Core\Entity\Controller\EntityController::title',
-      ])
-      ->setRequirements([
-        '_entity_access' => 'the_entity_type_id.view',
-        'the_entity_type_id' => '\d+',
-      ])
-      ->setOptions([
-        'parameters' => [
-          'the_entity_type_id' => [
-            'type' => 'entity:the_entity_type_id',
-          ],
-        ],
-      ]);
+    $route->setRequirement('the_entity_type_id', '\d+');
     $field_storage_definition = $this->prophesize(FieldStorageDefinitionInterface::class);
     $field_storage_definition->getType()->willReturn('integer');
-    $data['id_key_type_integer'] = [$entity_type4, $route4, $field_storage_definition];
+    $data['id_key_type_integer'] = [clone $route, $entity_type4->reveal(), $field_storage_definition->reveal()];
 
     return $data;
   }
 
+  /**
+   * @covers ::getEntityTypeIdKeyType
+   */
+  public function testGetEntityTypeIdKeyType() {
+    $entity_type = $this->prophesize(EntityTypeInterface::class);
+    $entity_type->isSubclassOf(FieldableEntityInterface::class)->willReturn(TRUE);
+    $entity_type->id()->willReturn('the_entity_type_id');
+    $entity_type->getKey('id')->willReturn('id');
+
+    $field_storage_definition = $this->prophesize(FieldStorageDefinitionInterface::class);
+    $field_storage_definition->getType()->willReturn('integer');
+    $this->entityFieldManager->getFieldStorageDefinitions('the_entity_type_id')->willReturn(['id' => $field_storage_definition]);
+
+    $type = $this->routeProvider->getEntityTypeIdKeyType($entity_type->reveal());
+    $this->assertSame('integer', $type);
+  }
+
+  /**
+   * @covers ::getEntityTypeIdKeyType
+   */
+  public function testGetEntityTypeIdKeyTypeNotFieldable() {
+    $entity_type = $this->prophesize(EntityTypeInterface::class);
+    $entity_type->isSubclassOf(FieldableEntityInterface::class)->willReturn(FALSE);
+    $this->entityFieldManager->getFieldStorageDefinitions(Argument::any())->shouldNotBeCalled();
+
+    $type = $this->routeProvider->getEntityTypeIdKeyType($entity_type->reveal());
+    $this->assertNull($type);
+  }
+
+  /**
+   * @param \Prophecy\Prophecy\ObjectProphecy $base_entity_type
+   * @return \Prophecy\Prophecy\ObjectProphecy
+   */
+  protected function getEntityType(ObjectProphecy $base_entity_type = NULL) {
+    $entity_type = $this->prophesize(EntityTypeInterface::class);
+    if ($base_entity_type) {
+      foreach ($base_entity_type->getMethodProphecies() as $method => $prophecies) {
+        foreach ($prophecies as $prophecy) {
+          $entity_type->addMethodProphecy(clone $prophecy);
+        }
+      }
+    }
+    return $entity_type;
+  }
+
 }
 
 class TestDefaultHtmlRouteProvider extends DefaultHtmlRouteProvider {
@@ -148,6 +260,9 @@ class TestDefaultHtmlRouteProvider extends DefaultHtmlRouteProvider {
   public function getEntityTypeIdKeyType(EntityTypeInterface $entity_type) {
     return parent::getEntityTypeIdKeyType($entity_type);
   }
+  public function getAddFormRoute(EntityTypeInterface $entity_type) {
+    return parent::getAddFormRoute($entity_type);
+  }
   public function getCanonicalRoute(EntityTypeInterface $entity_type) {
     return parent::getCanonicalRoute($entity_type);
   }
