diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 43d1eac..e50c6d5 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -2491,6 +2491,9 @@ function drupal_container(Container $new_container = NULL, $rebuild = FALSE) {
 
     // Register the EntityManager.
     $container->register('plugin.manager.entity', 'Drupal\Core\Entity\EntityManager');
+
+    // Register the EntityBundleManager.
+    $container->register('plugin.manager.entity_bundle', 'Drupal\Core\Entity\EntityBundleManager');
   }
   return $container;
 }
diff --git a/core/includes/entity.inc b/core/includes/entity.inc
index b3171ea..2f34e16 100644
--- a/core/includes/entity.inc
+++ b/core/includes/entity.inc
@@ -45,10 +45,34 @@ function entity_get_info($entity_type = NULL) {
 }
 
 /**
+ * Gets the entity bundle definitions for an entity type.
+ *
+ * @param string|null $entity_type
+ *   (optional) The entity type (e.g. 'node'). Leave NULL to retrieve
+ *   information for all entity types.
+ *
+ * @return array
+ *   An array containing the entity type's bundle definition, as retrieved with
+ *   \Drupal\Core\Entity\EntityBundleManager. If $entity_type is NULL, an
+ *   associative array bundle definitions for all entity types, keyed by entity
+ *   type is returned.
+ *
+ * @see \Drupal\Core\Entity\EntityBundleManager
+ * @see hook_entity_bnudle_info_alter()
+ */
+function entity_get_bundle_info($entity_type = NULL) {
+  // We're not calling getDefinitions() because we want to group them by entity
+  // type.
+  return drupal_container()->get('plugin.manager.entity_bundle')->getBundles($entity_type);
+}
+
+/**
  * Resets the cached information about entity types.
  */
 function entity_info_cache_clear() {
   drupal_static_reset('entity_get_info');
+  // Clear the entity bundle info as well.
+  drupal_container()->get('plugin.manager.entity_bundle')->clearCachedDefinitions();
   // Clear all languages.
   cache()->invalidateTags(array('entity_info' => TRUE));
 }
@@ -64,8 +88,7 @@ function entity_info_cache_clear() {
  *   bundle is defined.
  */
 function entity_get_bundles($entity_type) {
-  $entity_info = entity_get_info($entity_type);
-  return isset($entity_info['bundles']) ? array_keys($entity_info['bundles']) : array($entity_type);
+  return array_keys(entity_get_bundle_info($entity_type));
 }
 
 /**
diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php
index 71ef37b..074b70b 100644
--- a/core/lib/Drupal/Core/CoreBundle.php
+++ b/core/lib/Drupal/Core/CoreBundle.php
@@ -81,6 +81,9 @@ public function build(ContainerBuilder $container) {
     // Register the EntityManager.
     $container->register('plugin.manager.entity', 'Drupal\Core\Entity\EntityManager');
 
+    // Register the EntityBundleManager.
+    $container->register('plugin.manager.entity_bundle', 'Drupal\Core\Entity\EntityBundleManager');
+
     // The 'request' scope and service enable services to depend on the Request
     // object and get reconstructed when the request object changes (e.g.,
     // during a subrequest).
diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php
index 979614f..22deb37 100644
--- a/core/lib/Drupal/Core/Entity/Entity.php
+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -153,6 +153,8 @@ public function uri() {
     // A bundle-specific callback takes precedence over the generic one for the
     // entity type.
     $entity_info = $this->entityInfo();
+    // @todo Bring back support for using the bundle's uri_callback if it's a
+    // config entity.
     if (isset($entity_info['bundles'][$bundle]['uri_callback'])) {
       $uri_callback = $entity_info['bundles'][$bundle]['uri_callback'];
     }
diff --git a/core/lib/Drupal/Core/Entity/EntityBundleManager.php b/core/lib/Drupal/Core/Entity/EntityBundleManager.php
new file mode 100644
index 0000000..1f19701
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityBundleManager.php
@@ -0,0 +1,111 @@
+<?php
+
+namespace Drupal\Core\Entity;
+
+use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator;
+use Drupal\Component\Plugin\Discovery\ProcessDecorator;
+use Drupal\Component\Plugin\Factory\ReflectionFactory;
+use Drupal\Core\Plugin\Discovery\AlterDecorator;
+use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
+use Drupal\Core\Plugin\Discovery\CacheDecorator;
+
+/**
+ * Plugin type manager for entity bundles.
+ */
+class EntityBundleManager extends PluginManagerBase {
+
+  /**
+   * Default values for optional keys of the entity bundle plugin definition.
+   *
+   * @var array
+   */
+  protected $defaults = array(
+    'config_entity_type' => NULL,
+    'config_prefix' => NULL,
+    'admin' => NULL,
+  );
+
+  /**
+   * Contains the entity info of all entity types.
+   *
+   * @var array
+   *
+   * @see entity_get_info()
+   */
+  protected $entityInfo;
+
+  /**
+   * Constructs a new EntityBundleManager object.
+   */
+  public function __construct() {
+    $this->discovery = new AnnotatedClassDiscovery('Core', 'EntityBundle');
+    $this->discovery = new DerivativeDiscoveryDecorator($this->discovery);
+    $this->discovery = new ProcessDecorator($this->discovery, array($this, 'processDefinition'));
+    $this->discovery = new AlterDecorator($this->discovery, 'entity_bundle_info');
+    // @todo Add the 'entity_info' cache tag when http://drupal.org/node/1722882
+    // is fixed.
+    $this->discovery = new CacheDecorator($this->discovery, 'entity_bundle_info:' . language(LANGUAGE_TYPE_INTERFACE)->langcode);
+
+    $this->factory = new ReflectionFactory($this->discovery);
+    $this->entityInfo = entity_get_info();
+  }
+
+  /**
+   * Returns information about existing bundles.
+   *
+   * @param string|null $entity type.
+   *   The type of entity; e.g. 'node' or 'user'. Defaults to NULL.
+   *
+   * @return array
+   *   An array of bundles for the $entity_type keyed by bundle name, or, if no
+   *   $entity_type was provided, the array of all existing bundles, keyed by
+   *   entity type.
+   */
+  public function getBundles($entity_type = NULL) {
+    $plugins = array();
+
+    foreach ($this->getDefinitions() as $plugin) {
+      $plugins[$plugin['entity_type']][$plugin['id']] = $plugin;
+    }
+
+    if (empty($entity_type)) {
+      return $plugins;
+    }
+    elseif (isset($plugins[$entity_type])) {
+      return $plugins[$entity_type];
+    }
+  }
+
+  /**
+   * Overrides PluginManagerBase::processDefinition().
+   */
+  public function processDefinition(&$definition, $plugin_id) {
+    parent::processDefinition($definition, $plugin_id);
+
+    // If the definition comes from an alter hook it may not have the entity
+    // class name so add it now.
+    if (!isset($definition['class'])) {
+      $definition['class'] = $this->entityInfo[$definition['entity_type']]['class'];
+    }
+
+    // Add information about admin UI settings.
+    if (isset($this->entityInfo[$definition['entity_type']]['bundles']['admin'])) {
+      $admin = &$definition['admin'];
+      $entity_info = $this->entityInfo[$definition['entity_type']];
+      $admin = array(
+        'path' => $entity_info['bundles']['admin']['path'],
+        'access callback' => isset($entity_info['bundles']['admin']['access callback']) ? $entity_info['bundles']['admin']['access callback'] : NULL,
+        'access arguments' => isset($entity_info['bundles']['admin']['access arguments']) ? $entity_info['bundles']['admin']['access arguments'] : NULL,
+      );
+      if (isset($entity_info['bundles']['admin']['bundle argument'])) {
+        // Add the 'bundle argument'.
+        $admin['bundle argument'] = $entity_info['bundles']['admin']['bundle argument'];
+        // And the 'real path'.
+        $parts = explode('/', $entity_info['bundles']['admin']['path']);
+        $parts[$definition['admin']['bundle argument']] = $definition['id'];
+        $admin['real path'] = implode('/', $parts);
+      }
+    }
+  }
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index 34feb10..3dfad62 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -107,25 +107,17 @@
  *   Elements:
  *   - bundle: The name of the property that contains the name of the bundle
  *     object.
- * - bundles: An array describing all bundles for this object type. Keys are
- *   bundle machine names, as found in the objects' 'bundle' property
- *   (defined in the 'entity_keys' entry for the entity type in the
- *   EntityManager). Elements:
- *   - label: The human-readable name of the bundle.
- *   - uri_callback: The same as the 'uri_callback' key defined for the entity
- *     type in the EntityManager, but for the bundle only. When determining
- *     the URI of an entity, if a 'uri_callback' is defined for both the
- *     entity type and the bundle, the one for the bundle is used.
+ * - bundles: An array describing the bundle configuration for this entity type.
+ *   Elements:
+ *   - config_entity_type: @todo
+ *   - config_prefix: @todo
  *   - admin: An array of information that allows Field UI pages to attach
  *     themselves to the existing administration pages for the bundle.
  *     Elements:
  *     - path: the path of the bundle's main administration page, as defined
  *       in hook_menu(). If the path includes a placeholder for the bundle,
- *       the 'bundle argument', 'bundle helper' and 'real path' keys below
- *       are required.
+ *       the 'bundle argument' key below is required.
  *     - bundle argument: The position of the placeholder in 'path', if any.
- *     - real path: The actual path (no placeholder) of the bundle's main
- *       administration page. This will be used to generate links.
  *     - access callback: As in hook_menu(). 'user_access' will be assumed if
  *       no value is provided.
  *     - access arguments: As in hook_menu().
@@ -286,11 +278,6 @@ public function processDefinition(&$definition, $plugin_id) {
       );
     }
 
-    // If no bundle key is provided, assume a single bundle, named after
-    // the entity type.
-    if (empty($definition['entity_keys']['bundle']) && empty($definition['bundles'])) {
-      $definition['bundles'] = array($plugin_id => array('label' => $definition['label']));
-    }
     // Prepare entity schema fields SQL info for
     // Drupal\Core\Entity\DatabaseStorageControllerInterface::buildQuery().
     if (isset($definition['base_table'])) {
diff --git a/core/lib/Drupal/Core/Plugin/Core/EntityBundle/BundleBase.php b/core/lib/Drupal/Core/Plugin/Core/EntityBundle/BundleBase.php
new file mode 100644
index 0000000..ea37c07
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Core/EntityBundle/BundleBase.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Drupal\Core\Plugin\Core\Entity\Bundle;
+
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * @todo
+ *
+ * @Plugin(
+ *   id = "entity",
+ *   label = @Translation("Entity bundle"),
+ *   derivative = "Drupal\Core\Plugin\Derivative\EntityBundleDerivative"
+ * )
+ */
+class BundleBase { }
diff --git a/core/lib/Drupal/Core/Plugin/Derivative/EntityBundleDerivative.php b/core/lib/Drupal/Core/Plugin/Derivative/EntityBundleDerivative.php
new file mode 100644
index 0000000..d2957a8
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Derivative/EntityBundleDerivative.php
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Plugin\Derivative\EntityBundleDerivative.
+ */
+
+namespace Drupal\Core\Plugin\Derivative;
+
+use Drupal\Component\Plugin\Derivative\DerivativeInterface;
+
+/**
+ * Provides a resource plugin definition for every entity type.
+ */
+class EntityBundleDerivative implements DerivativeInterface {
+
+  /**
+   * List of derivative definitions.
+   *
+   * @var array
+   */
+  protected $derivatives;
+
+  /**
+   * Implements DerivativeInterface::getDerivativeDefinition().
+   */
+  public function getDerivativeDefinition($derivative_id, array $base_plugin_definition) {
+    $this->getDerivativeDefinitions($base_plugin_definition);
+    if (isset($this->derivatives[$derivative_id])) {
+      return $this->derivatives[$derivative_id];
+    }
+  }
+
+  /**
+   * Implements DerivativeInterface::getDerivativeDefinitions().
+   */
+  public function getDerivativeDefinitions(array $base_plugin_definition) {
+    $info = entity_get_info();
+    foreach ($info as $entity_type => $entity_info) {
+      switch ($entity_type) {
+        // @todo Remove this case when node types are converted to config.
+        case 'comment':
+          foreach (node_type_get_names() as $type => $name) {
+            $this->derivatives[$entity_type . ':' . 'comment_node_' . $type] = array(
+              'id' => 'comment_node_' . $type,
+              'entity_type' => $entity_type,
+              'label' => t('@node_type comment', array('@node_type' => $name)),
+              'class' => $entity_info['class'],
+              // Provide the node type/bundle name for other modules, so it does not
+              // have to be extracted manually from the bundle name.
+              'node bundle' => $type,
+            ) + $base_plugin_definition;
+          }
+          break;
+
+        // @todo Remove this case when node types are converted to config.
+        case 'node':
+          // Bundles must provide a human readable name so we can create help and error
+          // messages, and the path to attach Field admin pages to.
+          node_type_cache_reset();
+          foreach (node_type_get_names() as $type => $name) {
+            $this->derivatives[$entity_type . ':' . $type] = array(
+              'id' => $type,
+              'entity_type' => $entity_type,
+              'label' => $name,
+              'class' => $entity_info['class'],
+            ) + $base_plugin_definition;
+          }
+          break;
+
+        // @todo Remove this case when vocabularies are converted to config.
+        case 'taxonomy_term':
+          foreach (taxonomy_vocabulary_get_names() as $machine_name => $vocabulary) {
+            $this->derivatives[$entity_type . ':' . $machine_name] = array(
+              'id' => $machine_name,
+              'entity_type' => $entity_type,
+              'label' => $vocabulary->name,
+              'class' => $entity_info['class'],
+            ) + $base_plugin_definition;
+          }
+          break;
+
+        default:
+          if (isset($entity_info['config_entity_type']) && isset($entity_info['config_prefix'])) {
+            $names = config_get_storage_names_with_prefix($entity_info['config_prefix']);
+            foreach ($names as $name) {
+              $config = config($name);
+              $id = $config->get($info[$entity_info['config_entity_type']]['entity_keys']['id']);
+              $this->derivatives[$entity_type . ':' . $id] = array(
+                'id' => $id,
+                'entity_type' => $entity_type,
+                'label' => $config->get($info[$entity_info['config_entity_type']]['entity_keys']['label'] ?: 'label'),
+                'class' => $entity_info['class'],
+              ) + $base_plugin_definition;
+            }
+          }
+          else {
+            $this->derivatives[$entity_type . ':' . $entity_type] = array(
+              'id' => $entity_type,
+              'entity_type' => $entity_type,
+              'label' => $entity_info['label'],
+              'class' => $entity_info['class'],
+            ) + $base_plugin_definition;
+          }
+          break;
+      }
+    }
+    return $this->derivatives;
+  }
+}
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 38adbd0..a76beea 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -98,32 +98,6 @@ function comment_help($path, $arg) {
 }
 
 /**
- * Implements hook_entity_info().
- */
-function comment_entity_info(&$info) {
-  foreach (node_type_get_names() as $type => $name) {
-    $info['comment']['bundles']['comment_node_' . $type] = array(
-      'label' => t('@node_type comment', array('@node_type' => $name)),
-      // Provide the node type/bundle name for other modules, so it does not
-      // have to be extracted manually from the bundle name.
-      'node bundle' => $type,
-      'admin' => array(
-        // Place the Field UI paths for comments one level below the
-        // corresponding paths for nodes, so that they appear in the same set
-        // of local tasks. Note that the paths use a different placeholder name
-        // and thus a different menu loader callback, so that Field UI page
-        // callbacks get a comment bundle name from the node type in the URL.
-        // See comment_node_type_load() and comment_menu_alter().
-        'path' => 'admin/structure/types/manage/%comment_node_type/comment',
-        'bundle argument' => 4,
-        'real path' => 'admin/structure/types/manage/' . $type . '/comment',
-        'access arguments' => array('administer content types'),
-      ),
-    );
-  }
-}
-
-/**
  * Loads the comment bundle name corresponding a given content type.
  *
  * This function is used as a menu loader callback in comment_menu().
diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php b/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php
index e0bbfd9..ae87a64 100644
--- a/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php
+++ b/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php
@@ -35,6 +35,13 @@
  *     "label" = "subject",
  *     "uuid" = "uuid"
  *   },
+ *   bundles = {
+ *     "admin" = {
+ *       "path" = "admin/structure/types/manage/%comment_node_type/comment",
+ *       "bundle argument" = 4,
+ *       "access arguments" = {"administer content types"}
+ *     }
+ *   },
  *   view_modes = {
  *     "full" = {
  *       "label" = "Full comment",
diff --git a/core/modules/field/field.info.inc b/core/modules/field/field.info.inc
index b659c97..8029fc6 100644
--- a/core/modules/field/field.info.inc
+++ b/core/modules/field/field.info.inc
@@ -276,31 +276,6 @@ function field_info_storage_types($storage_type = NULL) {
 }
 
 /**
- * Returns information about existing bundles.
- *
- * @param $entity_type
- *   The type of entity; e.g. 'node' or 'user'.
- *
- * @return
- *   An array of bundles for the $entity_type keyed by bundle name, or, if no
- *   $entity_type was provided, the array of all existing bundles, keyed by
- *   entity type.
- */
-function field_info_bundles($entity_type = NULL) {
-  $info = entity_get_info();
-
-  if ($entity_type) {
-    return isset($info[$entity_type]['bundles']) ? $info[$entity_type]['bundles'] : array();
-  }
-
-  $bundles = array();
-  foreach ($info as $type => $entity_info) {
-    $bundles[$type] = $entity_info['bundles'];
-  }
-  return $bundles;
-}
-
-/**
  * Returns all field definitions.
  *
  * Use of this function should be avoided when possible, since it loads and
diff --git a/core/modules/field/tests/modules/field_test/field_test.entity.inc b/core/modules/field/tests/modules/field_test/field_test.entity.inc
index b48f5cf..d41275b 100644
--- a/core/modules/field/tests/modules/field_test/field_test.entity.inc
+++ b/core/modules/field/tests/modules/field_test/field_test.entity.inc
@@ -30,12 +30,43 @@ function field_test_entity_info_alter(&$entity_info) {
           'custom_settings' => TRUE,
         ),
       );
-      $entity_info[$entity_type]['bundles'] = variable_get('field_test_bundles', array('test_bundle' => array('label' => 'Test Bundle')));
+    }
+  }
+}
+
+/**
+ * Implements hook_entity_bundle_info_alter().
+ */
+function field_test_entity_bundle_info_alter(&$entity_bundle_info) {
+  $entity_info = entity_get_info();
+  foreach ($entity_info as $entity_type => $info) {
+    if ($info['module'] == 'field_test') {
+      $test_bundle = variable_get('field_test_bundles', array('test_bundle' => array('label' => 'Test Bundle')));
+      $test_bundle_id = key($test_bundle);
+      $entity_bundle_info[$entity_type . ':' . $test_bundle_id] = array(
+        'id' => $test_bundle_id,
+        'entity_type' => $entity_type,
+        'label' => $test_bundle[$test_bundle_id]['label'],
+      );
+
       if ($entity_type == 'test_entity_bundle') {
-        $entity_info[$entity_type]['bundles'] += array('test_entity_2' => array('label' => 'Test entity 2'));
+        $entity_bundle_info[$entity_type . ':test_entity_2'] = array(
+          'id' => 'test_entity_2',
+          'entity_type' => $entity_type,
+          'label' => 'Test entity 2',
+        );
       }
       if ($entity_type == 'test_entity_bundle_key') {
-        $entity_info[$entity_type]['bundles'] += array('bundle1' => array('label' => 'Bundle1'), 'bundle2' => array('label' => 'Bundle2'));
+        $entity_bundle_info[$entity_type . ':bundle1'] = array(
+          'id' => 'bundle1',
+          'entity_type' => $entity_type,
+          'label' => 'Bundle1',
+        );
+        $entity_bundle_info[$entity_type . ':bundle2'] = array(
+          'id' => 'bundle2',
+          'entity_type' => $entity_type,
+          'label' => 'Bundle2',
+        );
       }
     }
   }
diff --git a/core/modules/field/tests/modules/field_test/field_test.module b/core/modules/field/tests/modules/field_test/field_test.module
index 26ef868..50855aa 100644
--- a/core/modules/field/tests/modules/field_test/field_test.module
+++ b/core/modules/field/tests/modules/field_test/field_test.module
@@ -43,7 +43,7 @@ function field_test_permission() {
  */
 function field_test_menu() {
   $items = array();
-  $bundles = field_info_bundles('test_entity');
+  $bundles = entity_get_bundle_info('test_entity');
 
   foreach ($bundles as $bundle_name => $bundle_info) {
     $items['test-entity/add/' . $bundle_name] = array(
diff --git a/core/modules/field_ui/field_ui.admin.inc b/core/modules/field_ui/field_ui.admin.inc
index c51a309..460d26a 100644
--- a/core/modules/field_ui/field_ui.admin.inc
+++ b/core/modules/field_ui/field_ui.admin.inc
@@ -17,7 +17,7 @@
 function field_ui_fields_list() {
   $instances = field_info_instances();
   $field_types = field_info_field_types();
-  $bundles = field_info_bundles();
+  $bundles = entity_get_bundle_info();
 
   $modules = system_rebuild_module_data();
 
@@ -652,7 +652,7 @@ function field_ui_widget_type_form($form, &$form_state, FieldInstance $instance)
   $field = field_info_field($field_name);
   $field_type = field_info_field_types($field['type']);
   $widget_definition = $instance->getWidget()->getDefinition();
-  $bundles = field_info_bundles();
+  $bundles = entity_get_bundle_info();
   $bundle_label = $bundles[$entity_type][$bundle]['label'];
 
   $form = array(
@@ -761,7 +761,7 @@ function field_ui_field_delete_form_submit($form, &$form_state) {
 
   $field = field_info_field($field_name);
   $instance = field_info_instance($entity_type, $field_name, $bundle);
-  $bundles = field_info_bundles();
+  $bundles = entity_get_bundle_info();
   $bundle_label = $bundles[$entity_type][$bundle]['label'];
 
   if (!empty($bundle) && $field && !$field['locked'] && $form_values['confirm']) {
@@ -816,7 +816,7 @@ function field_ui_field_edit_form($form, &$form_state, $instance) {
 
   $field_type = field_info_field_types($field['type']);
   $widget_type = field_info_widget_types($instance['widget']['type']);
-  $bundles = field_info_bundles();
+  $bundles = entity_get_bundle_info();
 
   // Create a form structure for the instance values.
   $form['instance'] = array(
diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module
index a04c0dd..4cd8e2c 100644
--- a/core/modules/field_ui/field_ui.module
+++ b/core/modules/field_ui/field_ui.module
@@ -69,9 +69,10 @@ function field_ui_menu() {
   );
 
   // Create tabs for all possible bundles.
+  $bundles = drupal_container()->get('plugin.manager.entity_bundle')->getBundles();
   foreach (entity_get_info() as $entity_type => $entity_info) {
-    if ($entity_info['fieldable']) {
-      foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
+    if ($entity_info['fieldable'] && isset($bundles[$entity_type])) {
+      foreach ($bundles[$entity_type] as $bundle_info) {
         if (isset($bundle_info['admin'])) {
           // Extract path information from the bundle.
           $path = $bundle_info['admin']['path'];
@@ -87,7 +88,7 @@ function field_ui_menu() {
             $bundle_pos = (string) $bundle_arg;
           }
           else {
-            $bundle_arg = $bundle_name;
+            $bundle_arg = $bundle_info['id'];
             $bundle_pos = '0';
           }
           // This is the position of the %field_ui_menu placeholder in the
@@ -317,7 +318,7 @@ function field_ui_field_attach_create_bundle($entity_type, $bundle) {
  * Determines the adminstration path for a bundle.
  */
 function field_ui_bundle_admin_path($entity_type, $bundle_name) {
-  $bundles = field_info_bundles($entity_type);
+  $bundles = entity_get_bundle_info($entity_type);
   $bundle_info = $bundles[$bundle_name];
   if (isset($bundle_info['admin'])) {
     return isset($bundle_info['admin']['real path']) ? $bundle_info['admin']['real path'] : $bundle_info['admin']['path'];
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
index 1fd8fc8..f9ca243 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
@@ -39,6 +39,13 @@
  *   bundle_keys = {
  *     "bundle" = "type"
  *   },
+ *   bundles = {
+ *     "admin" = {
+ *       "path" = "admin/structure/types/manage/%node_type",
+ *       "bundle argument" = 4,
+ *       "access arguments" = {"administer content types"}
+ *     }
+ *   },
  *   view_modes = {
  *     "full" = {
  *       "label" = "Full content",
diff --git a/core/modules/node/lib/Drupal/node/Plugin/views/wizard/Node.php b/core/modules/node/lib/Drupal/node/Plugin/views/wizard/Node.php
index fec2bfe..6390e31 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/views/wizard/Node.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/views/wizard/Node.php
@@ -270,7 +270,7 @@ protected function build_filters(&$form, &$form_state) {
     // entities. If a particular entity type (i.e., bundle) has been
     // selected above, then we only search for taxonomy fields associated
     // with that bundle. Otherwise, we use all bundles.
-    $bundles = array_keys($entity_info['bundles']);
+    $bundles = entity_get_bundles($entity_info['id']);
     // Double check that this is a real bundle before using it (since above
     // we added a dummy option 'all' to the bundle list on the form).
     if (isset($selected_bundle) && in_array($selected_bundle, $bundles)) {
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 71a57f2..b6341fa 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -194,21 +194,6 @@ function node_entity_info(&$info) {
       'custom_settings' => FALSE,
     );
   }
-
-  // Bundles must provide a human readable name so we can create help and error
-  // messages, and the path to attach Field admin pages to.
-  node_type_cache_reset();
-  foreach (node_type_get_names() as $type => $name) {
-    $info['node']['bundles'][$type] = array(
-      'label' => $name,
-      'admin' => array(
-        'path' => 'admin/structure/types/manage/%node_type',
-        'real path' => 'admin/structure/types/manage/' . $type,
-        'bundle argument' => 4,
-        'access arguments' => array('administer content types'),
-      ),
-    );
-  }
 }
 
 /**
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
index c0a59e0..0f0aea2 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
@@ -327,6 +327,7 @@ protected function drupalCreateContentType($settings = array()) {
 
     $saved_type = node_type_save($type);
     node_types_rebuild();
+    drupal_container()->get('plugin.manager.entity_bundle')->clearCachedDefinitions();
     menu_router_rebuild();
     node_add_body_field($type);
 
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php
index 51468af..f560c52 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php
@@ -37,6 +37,14 @@
  *   bundle_keys = {
  *     "bundle" = "machine_name"
  *   },
+ *   bundles = {
+ *     "admin" = {
+ *       "path" = "admin/structure/taxonomy/%taxonomy_vocabulary_machine_name",
+ *       "bundle argument" = 3,
+ *       "access arguments" = {"administer taxonomy"}
+ *     }
+ *   },
+
  *   view_modes = {
  *     "full" = {
  *       "label" = "Taxonomy term page",
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/VocabularyUnitTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/VocabularyUnitTest.php
index 9080ef2..89f8710 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/VocabularyUnitTest.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/VocabularyUnitTest.php
@@ -187,9 +187,9 @@ function testTaxonomyVocabularyChangeMachineName() {
     taxonomy_vocabulary_save($this->vocabulary);
 
     // Check that entity bundles are properly updated.
-    $info = entity_get_info('taxonomy_term');
-    $this->assertFalse(isset($info['bundles'][$old_name]), 'The old bundle name does not appear in entity_get_info().');
-    $this->assertTrue(isset($info['bundles'][$new_name]), 'The new bundle name appears in entity_get_info().');
+    $info = entity_get_bundle_info('taxonomy_term');
+    $this->assertFalse(isset($info[$old_name]), 'The old bundle name does not appear in entity_get_bundle_info().');
+    $this->assertTrue(isset($info[$new_name]), 'The new bundle name appears in entity_get_bundle_info().');
 
     // Check that the field instance is still attached to the vocabulary.
     $this->assertTrue(field_info_instance('taxonomy_term', 'field_test', $new_name), 'The bundle name was updated correctly.');
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index d8ea5b7..9d0b4eb 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -106,23 +106,6 @@ function taxonomy_permission() {
 }
 
 /**
- * Implements hook_entity_info().
- */
-function taxonomy_entity_info(&$info) {
-  foreach (taxonomy_vocabulary_get_names() as $machine_name => $vocabulary) {
-    $info['taxonomy_term']['bundles'][$machine_name] = array(
-      'label' => $vocabulary->name,
-      'admin' => array(
-        'path' => 'admin/structure/taxonomy/%taxonomy_vocabulary_machine_name',
-        'real path' => 'admin/structure/taxonomy/' . $machine_name,
-        'bundle argument' => 3,
-        'access arguments' => array('administer taxonomy'),
-      ),
-    );
-  }
-}
-
-/**
  * Entity URI callback.
  */
 function taxonomy_term_uri($term) {
@@ -136,8 +119,7 @@ function taxonomy_term_uri($term) {
  */
 function taxonomy_field_extra_fields() {
   $return = array();
-  $info = entity_get_info('taxonomy_term');
-  foreach (array_keys($info['bundles']) as $bundle) {
+  foreach (entity_get_bundles('taxonomy_term') as $bundle) {
     $return['taxonomy_term'][$bundle] = array(
       'form' => array(
         'name' => array(
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php b/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php
index 99aeac8..ea19198 100644
--- a/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php
+++ b/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php
@@ -35,12 +35,9 @@
  *     "uuid" = "uuid"
  *   },
  *   bundles = {
- *     "user" = {
- *       "label" = "User",
- *       "admin" = {
- *         "path" = "admin/config/people/accounts",
- *         "access arguments" = {"administer users"}
- *       }
+ *     "admin" = {
+ *       "path" = "admin/config/people/accounts",
+ *       "access arguments" = {"administer users"}
  *     }
  *   },
  *   view_modes = {
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/wizard/WizardPluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/wizard/WizardPluginBase.php
index 0e8546c..7325606 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/wizard/WizardPluginBase.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/wizard/WizardPluginBase.php
@@ -560,11 +560,12 @@ protected function build_filters(&$form, &$form_state) {
     $fields = views_fetch_fields($this->base_table, 'filter');
 
     $entity_info = $this->entity_info;
+    $entity_bundle_info = entity_get_bundle_info($entity_info['id']);
     // If the current base table support bundles and has more than one (like user).
-    if (isset($entity_info['bundle_keys']) && isset($entity_info['bundles'])) {
+    if (isset($entity_info['bundle_keys']) && count($entity_bundle_info) > 1) {
       // Get all bundles and their human readable names.
       $options = array('all' => t('All'));
-      foreach ($entity_info['bundles'] as $type => $bundle) {
+      foreach ($entity_bundle_info as $type => $bundle) {
         $options[$type] = $bundle['label'];
       }
       $form['displays']['show']['type'] = array(
