diff --git a/core/core.services.yml b/core/core.services.yml
index 05be1bb..a07a0d8 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -297,6 +297,11 @@ services:
     tags:
       - { name: module_install.uninstall_validator }
     arguments: ['@entity.manager', '@string_translation']
+  field_uninstall_validator:
+    class: Drupal\Core\Field\FieldModuleUninstallValidator
+    tags:
+      - { name: module_install.uninstall_validator }
+    arguments: ['@entity.manager', '@string_translation']
   theme_handler:
     class: Drupal\Core\Extension\ThemeHandler
     arguments: ['@app.root', '@config.factory', '@module_handler', '@state', '@info_parser', '@logger.channel.default', '@asset.css.collection_optimizer', '@config.installer', '@config.manager', '@router.builder_indicator']
diff --git a/core/lib/Drupal/Core/Entity/DynamicallyFieldableEntityStorageInterface.php b/core/lib/Drupal/Core/Entity/DynamicallyFieldableEntityStorageInterface.php
index a5a9a94..5037628 100644
--- a/core/lib/Drupal/Core/Entity/DynamicallyFieldableEntityStorageInterface.php
+++ b/core/lib/Drupal/Core/Entity/DynamicallyFieldableEntityStorageInterface.php
@@ -20,7 +20,16 @@
  *
  * For example, configurable fields defined and exposed by field.module.
  */
-interface DynamicallyFieldableEntityStorageInterface extends EntityStorageInterface, FieldStorageDefinitionListenerInterface {
+interface DynamicallyFieldableEntityStorageInterface extends FieldableEntityStorageInterface, FieldStorageDefinitionListenerInterface {
+
+  /**
+   * Determines if the storage contains any data.
+   *
+   * @return bool
+   *   TRUE if the storage contains data, FALSE if not.
+   */
+  public function hasData();
+
   /**
    * Reacts to the creation of a field.
    *
@@ -68,31 +77,6 @@ public function onFieldDefinitionDelete(FieldDefinitionInterface $field_definiti
   public function purgeFieldData(FieldDefinitionInterface $field_definition, $batch_size);
 
   /**
-   * Determines the number of entities with values for a given field.
-   *
-   * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
-   *   The field for which to count data records.
-   * @param bool $as_bool
-   *   (Optional) Optimises the query for checking whether there are any records
-   *   or not. Defaults to FALSE.
-   *
-   * @return bool|int
-   *   The number of entities. If $as_bool parameter is TRUE then the
-   *   value will either be TRUE or FALSE.
-   *
-   * @see \Drupal\Core\Entity\FieldableEntityStorageInterface::purgeFieldData()
-   */
-  public function countFieldData($storage_definition, $as_bool = FALSE);
-
-  /**
-   * Determines if the storage contains any data.
-   *
-   * @return bool
-   *   TRUE if the storage contains data, FALSE if not.
-   */
-  public function hasData();
-
-  /**
    * Performs final cleanup after all data of a field has been purged.
    *
    * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
diff --git a/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php b/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php
index 24a74be..5330ad5 100644
--- a/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php
+++ b/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php
@@ -7,8 +7,6 @@
 
 namespace Drupal\Core\Entity;
 
-use Drupal\Core\TypedData\ComplexDataInterface;
-
 /**
  * Interface for entities having fields.
  *
diff --git a/core/lib/Drupal/Core/Entity/FieldableEntityStorageInterface.php b/core/lib/Drupal/Core/Entity/FieldableEntityStorageInterface.php
new file mode 100644
index 0000000..3576744
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/FieldableEntityStorageInterface.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface.
+ */
+
+namespace Drupal\Core\Entity;
+
+/**
+ * A storage that supports entity types with dynamic field definitions.
+ *
+ * A storage that implements this interface can react to the entity type's field
+ * definitions changing, due to modules being installed or uninstalled, or via
+ * field UI, or via code changes to the entity class.
+ *
+ * For example, configurable fields defined and exposed by field.module.
+ */
+interface FieldableEntityStorageInterface extends EntityStorageInterface {
+
+  /**
+   * Determines the number of entities with values for a given field.
+   *
+   * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
+   *   The field for which to count data records.
+   * @param bool $as_bool
+   *   (Optional) Optimises the query for checking whether there are any records
+   *   or not. Defaults to FALSE.
+   *
+   * @return bool|int
+   *   The number of entities. If $as_bool parameter is TRUE then the
+   *   value will either be TRUE or FALSE.
+   *
+   * @see \Drupal\Core\Entity\FieldableEntityStorageInterface::purgeFieldData()
+   */
+  public function countFieldData($storage_definition, $as_bool = FALSE);
+
+}
diff --git a/core/lib/Drupal/Core/Field/FieldModuleUninstallValidator.php b/core/lib/Drupal/Core/Field/FieldModuleUninstallValidator.php
new file mode 100644
index 0000000..fa3fecc
--- /dev/null
+++ b/core/lib/Drupal/Core/Field/FieldModuleUninstallValidator.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Field\FieldModuleUninstallValidator.
+ */
+
+namespace Drupal\Core\Field;
+
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Entity\FieldableEntityStorageInterface;
+use Drupal\Core\Extension\ModuleUninstallValidatorInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+
+/**
+ * Validates module uninstall readiness based on defined storage definitions.
+ *
+ * @todo Remove this once we support field purging for base fields. See
+ *   https://www.drupal.org/node/2282119.
+ */
+class FieldModuleUninstallValidator implements ModuleUninstallValidatorInterface {
+  use StringTranslationTrait;
+
+  /**
+   * Constructs the object.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+   *   The string translation service.
+   */
+  public function __construct(EntityManagerInterface $entity_manager, TranslationInterface $string_translation) {
+    $this->entityManager = $entity_manager;
+    $this->stringTranslation = $string_translation;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate($module_name) {
+    $reasons = array();
+
+    foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
+      // We skip entity types defined by the module as there must be no content
+      // to be able to uninstall them anyway. We also skip the Field module as
+      // it implements field purging.
+      // See \Drupal\Core\Entity\ContentUninstallValidator.
+      if ($entity_type->getProvider() != $module_name  && $entity_type instanceof FieldableEntityInterface) {
+        foreach ($this->entityManager->getFieldStorageDefinitions($entity_type_id) as $storage_definition) {
+          if ($module_name != 'field' && $storage_definition->getProvider() == $module_name) {
+            $storage = $this->entityManager->getStorage($entity_type_id);
+            if ($storage instanceof FieldableEntityStorageInterface && $storage->countFieldData($storage_definition, TRUE)) {
+              $reasons[] = $this->t('There is content for the field @field-name on entity type @entity_type.', array(
+                '@field-name' => $storage_definition->getName(),
+                '@entity_type' => $entity_type->getLabel(),
+              ));
+            }
+          }
+        }
+      }
+    }
+
+    return $reasons;
+  }
+
+}
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index 143e8c5..9264d31 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -118,40 +118,6 @@ function field_cron() {
 }
 
 /**
- * Implements hook_system_info_alter().
- *
- * Goes through a list of all modules that provide a field type and makes them
- * required if there are any active fields of that type.
- */
-function field_system_info_alter(&$info, Extension $file, $type) {
-  // It is not safe to call entity_load_multiple_by_properties() during
-  // maintenance mode.
-  if ($type == 'module' && !defined('MAINTENANCE_MODE')) {
-    $field_storages = entity_load_multiple_by_properties('field_storage_config', array('module' => $file->getName(), 'include_deleted' => TRUE));
-    if ($field_storages) {
-      $info['required'] = TRUE;
-
-      // Provide an explanation message (only mention pending deletions if there
-      // remains no actual, non-deleted fields)
-      $non_deleted = FALSE;
-      foreach ($field_storages as $field_storage) {
-        if (empty($field_storage->deleted)) {
-          $non_deleted = TRUE;
-          break;
-        }
-      }
-      if ($non_deleted) {
-        $explanation = t('Fields type(s) in use');
-      }
-      else {
-        $explanation = t('Fields pending deletion');
-      }
-      $info['explanation'] = $explanation;
-    }
-  }
-}
-
-/**
  * Implements hook_entity_field_storage_info().
  */
 function field_entity_field_storage_info(\Drupal\Core\Entity\EntityTypeInterface $entity_type) {
diff --git a/core/modules/field/field.services.yml b/core/modules/field/field.services.yml
new file mode 100644
index 0000000..7c33168
--- /dev/null
+++ b/core/modules/field/field.services.yml
@@ -0,0 +1,6 @@
+services:
+  field_module_uninstall_validator:
+    class: Drupal\field\ModuleUninstallValidator
+    tags:
+      - { name: module_install.uninstall_validator }
+    arguments: ['@entity.manager', '@string_translation']
diff --git a/core/modules/field/src/ModuleUninstallValidator.php b/core/modules/field/src/ModuleUninstallValidator.php
new file mode 100644
index 0000000..90f917e
--- /dev/null
+++ b/core/modules/field/src/ModuleUninstallValidator.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\field\ModuleUninstallValidator.
+ */
+
+namespace Drupal\field;
+
+use Drupal\Core\Entity\ContentEntityTypeInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\FieldableEntityStorageInterface;
+use Drupal\Core\Extension\ModuleUninstallValidatorInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+
+/**
+ * Validates module uninstall readiness based on defined storage definitions.
+ */
+class ModuleUninstallValidator implements ModuleUninstallValidatorInterface {
+  use StringTranslationTrait;
+
+  /**
+   * Constructs the object.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+   *   The string translation service.
+   */
+  public function __construct(EntityManagerInterface $entity_manager, TranslationInterface $string_translation) {
+    $this->entityManager = $entity_manager;
+    $this->stringTranslation = $string_translation;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate($module_name) {
+    $reasons = array();
+
+    // It is not safe to call entity_load_multiple_by_properties() during
+    // maintenance mode.
+    if (!defined('MAINTENANCE_MODE')) {
+      $field_storages = $this->entityManager
+        ->getStorage('field_storage_config')
+        ->loadByProperties(array('module' => $module_name, 'include_deleted' => TRUE));
+
+      if ($field_storages) {
+        // Provide an explanation message (only mention pending deletions if there
+        // remains no actual, non-deleted fields)
+        $non_deleted = FALSE;
+        foreach ($field_storages as $field_storage) {
+          if (empty($field_storage->deleted)) {
+            $non_deleted = TRUE;
+            break;
+          }
+        }
+
+        $reasons[] = $non_deleted ? $this->t('Fields type(s) in use') : $this->t('Fields pending deletion');
+      }
+    }
+
+    return $reasons;
+  }
+
+}
diff --git a/core/modules/system/src/Tests/Field/FieldModuleUninstallValidatorTest.php b/core/modules/system/src/Tests/Field/FieldModuleUninstallValidatorTest.php
new file mode 100644
index 0000000..d9feabc
--- /dev/null
+++ b/core/modules/system/src/Tests/Field/FieldModuleUninstallValidatorTest.php
@@ -0,0 +1,141 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\system\Tests\Field\FieldModuleUninstallValidatorTest.
+ */
+
+namespace Drupal\system\Tests\Field;
+
+use Drupal\Core\Extension\ModuleUninstallValidatorException;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\entity_test\FieldStorageDefinition;
+use Drupal\system\Tests\Entity\EntityUnitTestBase;
+
+/**
+ * Tests FieldModuleUninstallValidator functionality.
+ *
+ * @group Field
+ */
+class FieldModuleUninstallValidatorTest extends EntityUnitTestBase {
+
+  /**
+   * The entity definition update manager.
+   *
+   * @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface
+   */
+  protected $entityDefinitionUpdateManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->installSchema('user', 'users_data');
+    $this->entityDefinitionUpdateManager = $this->container->get('entity.definition_update_manager');
+
+    // Setup some fields for entity_test_extra to create.
+    $definitions['extra_base_field'] = BaseFieldDefinition::create('string')
+      ->setName('extra_base_field')
+      ->setTargetEntityTypeId('entity_test')
+      ->setTargetBundle('entity_test');
+    $this->state->set('entity_test.additional_base_field_definitions', $definitions);
+    // @todo: Use better field definition classes once there are any.
+    $definitions['extra_bundle_field'] = FieldStorageDefinition::create('string')
+      ->setName('extra_bundle_field')
+      ->setTargetEntityTypeId('entity_test')
+      ->setTargetBundle('entity_test');
+    $this->state->set('entity_test.additional_field_storage_definitions', $definitions);
+    $this->state->set('entity_test.entity_test.additional_bundle_field_definitions', $definitions);
+    $this->entityManager->clearCachedDefinitions();
+  }
+
+  /**
+   * Tests uninstall entity_test module with and without content for the field.
+   */
+  public function testUninstallingModule() {
+    // Test uninstall works fine without content.
+    $this->assertModuleInstallUninstall('entity_test_extra');
+
+    // Test uninstalling works fine with content having no field values.
+    $entity = $this->entityManager->getStorage('entity_test')->create([
+      'name' => $this->randomString(),
+    ]);
+    $entity->save();
+    $this->assertModuleInstallUninstall('entity_test_extra');
+    $entity->delete();
+
+    // Verify uninstall works fine without content again.
+    $this->assertModuleInstallUninstall('entity_test_extra');
+    // Verify uninstalling entity_test is not possible when there is content for
+    // the base field.
+    $this->enableModules(['entity_test_extra']);
+    $this->entityDefinitionUpdateManager->applyUpdates();
+    $entity = $this->entityManager->getStorage('entity_test')->create([
+      'name' => $this->randomString(),
+      'extra_base_field' => $this->randomString(),
+    ]);
+    $entity->save();
+
+    try {
+      $this->getModuleInstaller()->uninstall(array('entity_test_extra'));
+      $this->fail('Module uninstallation fails as the module provides a base field which has content.');
+    }
+    catch (ModuleUninstallValidatorException $e) {
+      $this->pass('Module uninstallation fails as the module provides a base field which has content.');
+    }
+
+    // Verify uninstalling entity_test is not possible when there is content for
+    // the bundle field.
+    $entity->delete();
+    $this->assertModuleInstallUninstall('entity_test_extra');
+    $this->enableModules(['entity_test_extra']);
+    $this->entityDefinitionUpdateManager->applyUpdates();
+    $entity = $this->entityManager->getStorage('entity_test')->create([
+      'name' => $this->randomString(),
+      'extra_bundle_field' => $this->randomString(),
+    ]);
+    $entity->save();
+    try {
+      $this->getModuleInstaller()->uninstall(array('entity_test_extra'));
+      $this->fail('Module uninstallation fails as the module provides a bundle field which has content.');
+    }
+    catch (ModuleUninstallValidatorException $e) {
+      $this->pass('Module uninstallation fails as the module provides a bundle field which has content.');
+    }
+  }
+
+  /**
+   * Asserts the given module can be installed and uninstalled.
+   *
+   * @param string $module_name
+   *   The module to install and uninstall.
+   */
+  protected function assertModuleInstallUninstall($module_name) {
+    $this->enableModules([$module_name]);
+    $this->entityDefinitionUpdateManager->applyUpdates();
+    $this->assertTrue($this->getModuleHandler()->moduleExists($module_name), $module_name .' module is enabled.');
+    $this->getModuleInstaller()->uninstall([$module_name]);
+    $this->entityDefinitionUpdateManager->applyUpdates();
+    $this->assertFalse($this->getModuleHandler()->moduleExists($module_name), $module_name . ' module is disabled.');
+  }
+
+  /**
+   * Returns the ModuleHandler.
+   *
+   * @return \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected function getModuleHandler() {
+    return $this->container->get('module_handler');
+  }
+
+  /**
+   * Returns the ModuleInstaller.
+   *
+   * @return \Drupal\Core\Extension\ModuleInstallerInterface
+   */
+  protected function getModuleInstaller() {
+    return $this->container->get('module_installer');
+  }
+
+}
diff --git a/core/modules/system/tests/modules/entity_test_extra/entity_test_extra.info.yml b/core/modules/system/tests/modules/entity_test_extra/entity_test_extra.info.yml
new file mode 100644
index 0000000..b5c1cc3
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test_extra/entity_test_extra.info.yml
@@ -0,0 +1,8 @@
+name: 'Entity test extra'
+type: module
+description: 'Provides extra fields for entity test entity types.'
+package: Testing
+version: VERSION
+core: 8.x
+dependencies:
+  - entity_test
diff --git a/core/modules/system/tests/modules/entity_test_extra/entity_test_extra.module b/core/modules/system/tests/modules/entity_test_extra/entity_test_extra.module
new file mode 100644
index 0000000..d55aab3
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test_extra/entity_test_extra.module
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Test module for the entity API providing several extra fields for testing.
+ */
+
+use Drupal\Core\Entity\EntityTypeInterface;
+
+/**
+ * Implements hook_entity_base_field_info().
+ */
+function entity_test_extra_entity_base_field_info(EntityTypeInterface $entity_type) {
+  return \Drupal::state()->get($entity_type->id() . '.additional_base_field_definitions', array());
+}
+
+/**
+ * Implements hook_entity_field_storage_info().
+ */
+function entity_test_extra_entity_field_storage_info(EntityTypeInterface $entity_type) {
+  return \Drupal::state()->get($entity_type->id() . '.additional_field_storage_definitions', array());
+}
+
+/**
+ * Implements hook_entity_bundle_field_info().
+ */
+function entity_test_extra_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
+  return \Drupal::state()->get($entity_type->id() . '.' . $bundle . '.additional_bundle_field_definitions', array());
+}
