diff --git a/core/includes/entity.api.php b/core/includes/entity.api.php
index e6e1f7c..fd9a0c6 100644
--- a/core/includes/entity.api.php
+++ b/core/includes/entity.api.php
@@ -630,17 +630,20 @@ function hook_entity_operation_alter(array &$operations, \Drupal\Core\Entity\Ent
  * @param string $operation
  *   The operation to be performed. See
  *   \Drupal\Core\TypedData\AccessibleInterface::access() for possible values.
- * @param \Drupal\Core\Entity\Field\Field $field
- *   The entity field object on which the operation is to be performed.
+ * @param \Drupal\Core\Entity\Field\FieldDefinitionInterface $field_definition
+ *   The field definition
  * @param \Drupal\Core\Session\AccountInterface $account
  *   The user account to check.
+ * @param \Drupal\Core\Entity\Field\FieldInterface $field
+ *   (optional) The entity field object on which the operation is to be
+ *   performed.
  *
  * @return bool|NULL
  *   TRUE if access should be allowed, FALSE if access should be denied and NULL
  *   if the implementation has no opinion.
  */
-function hook_entity_field_access($operation, $field, \Drupal\Core\Session\AccountInterface $account) {
-  if ($field->getName() == 'field_of_interest' && $operation == 'update') {
+function hook_entity_field_access($operation, \Drupal\Core\Entity\Field\FieldDefinitionInterface $field_definition, \Drupal\Core\Session\AccountInterface $account, \Drupal\Core\Entity\Field\FieldInterface $field) {
+  if ($field_definition->getFieldName() == 'field_of_interest' && $operation == 'update') {
     return user_access('update field of interest', $account);
   }
 }
@@ -658,9 +661,12 @@ function hook_entity_field_access($operation, $field, \Drupal\Core\Session\Accou
  * @param array $context
  *   Context array on the performed operation with the following keys:
  *   - operation: The operation to be performed (string).
- *   - field: The entity field object (\Drupal\Core\Entity\Field\Field).
+ *   - field_definition: The field definition object
+ *     (\Drupal\Core\Entity\Field\FieldDefinitionInterface)
  *   - account: The user account to check access for
  *     (Drupal\user\Entity\User).
+ *   - field: (optional) The entity field object
+ *     (\Drupal\Core\Entity\Field\FieldInterface).
  */
 function hook_entity_field_access_alter(array &$grants, array $context) {
   $field = $context['field'];
diff --git a/core/lib/Drupal/Core/Entity/EntityAccessController.php b/core/lib/Drupal/Core/Entity/EntityAccessController.php
index 47573c2..edbe9b6 100644
--- a/core/lib/Drupal/Core/Entity/EntityAccessController.php
+++ b/core/lib/Drupal/Core/Entity/EntityAccessController.php
@@ -8,6 +8,8 @@
 namespace Drupal\Core\Entity;
 
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Entity\Field\FieldDefinitionInterface;
+use Drupal\Core\Entity\Field\FieldInterface;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Session\AccountInterface;
 
@@ -260,4 +262,45 @@ public function setModuleHandler(ModuleHandlerInterface $module_handler) {
     return $this;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function fieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account = NULL, FieldInterface $field = NULL) {
+    global $user;
+    if (!isset($account)) {
+      $account = $user;
+    }
+
+    // Get the default access restriction that lives within this field.
+    $default = $field ? $field->defaultAccess($operation, $account) : TRUE;
+
+    // Invoke hook and collect grants/denies for field access from other
+    // modules. Our default access flag is masked under the ':default' key.
+    $grants = array(':default' => $default);
+    $hook_implementations = \Drupal::moduleHandler()->getImplementations('entity_field_access');
+    foreach ($hook_implementations as $module) {
+      $grants = array_merge($grants, array($module => module_invoke($module, 'entity_field_access', $operation, $field_definition, $account, $field)));
+    }
+
+    // Also allow modules to alter the returned grants/denies.
+    $context = array(
+      'operation' => $operation,
+      'field_definition' => $field_definition,
+      'field' => $field,
+      'account' => $account,
+    );
+    drupal_alter('entity_field_access', $grants, $context);
+
+    // One grant being FALSE is enough to deny access immediately.
+    if (in_array(FALSE, $grants, TRUE)) {
+      return FALSE;
+    }
+    // At least one grant has the explicit opinion to allow access.
+    if (in_array(TRUE, $grants, TRUE)) {
+      return TRUE;
+    }
+    // All grants are NULL and have no opinion - deny access in that case.
+    return FALSE;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityAccessControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityAccessControllerInterface.php
index 749b9a0..8a62727 100644
--- a/core/lib/Drupal/Core/Entity/EntityAccessControllerInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityAccessControllerInterface.php
@@ -8,6 +8,8 @@
 namespace Drupal\Core\Entity;
 
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Entity\Field\FieldDefinitionInterface;
+use Drupal\Core\Entity\Field\FieldInterface;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Session\AccountInterface;
 
@@ -70,4 +72,22 @@ public function resetCache();
    */
   public function setModuleHandler(ModuleHandlerInterface $module_handler);
 
+  /**
+   * Checks access to an operation on a given entity field.
+   *
+   * @param string $operation
+   *   The operation access should be checked for.
+   *   Usually one of "view" or "edit".
+   * @param \Drupal\Core\Entity\Field\FieldDefinitionInterface $field_definition
+   *   The field definition.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *  (optional) The user session for which to check access, or NULL to check
+   *   access for the current user. Defaults to NULL.
+   * @param \Drupal\Core\Entity\Field\FieldInterface $field
+   *   (optional) The field values for which to check access, or NULL if access
+   *    is checked for the field definition, without any specific value
+   *    available. Defaults to NULL.
+   */
+  public function fieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account = NULL, FieldInterface $field = NULL);
+
 }
diff --git a/core/lib/Drupal/Core/Entity/Field/Field.php b/core/lib/Drupal/Core/Entity/Field/Field.php
index 6f8382a..35bc7de 100644
--- a/core/lib/Drupal/Core/Entity/Field/Field.php
+++ b/core/lib/Drupal/Core/Entity/Field/Field.php
@@ -81,8 +81,7 @@ public function getLangcode() {
    * {@inheritdoc}
    */
   public function getFieldDefinition() {
-    // @todo https://drupal.org/node/1988612
-    return NULL;
+    return new FieldDefinition($this->definition);
   }
 
   /**
@@ -197,52 +196,15 @@ public function __unset($property_name) {
   }
 
   /**
-   * Implements \Drupal\Core\TypedData\AccessibleInterface::access().
+   * {@inheritdoc}
    */
   public function access($operation = 'view', AccountInterface $account = NULL) {
-    global $user;
-    if (!isset($account)) {
-      $account = $user;
-    }
-    // Get the default access restriction that lives within this field.
-    $access = $this->defaultAccess($operation, $account);
-    // Invoke hook and collect grants/denies for field access from other
-    // modules. Our default access flag is masked under the ':default' key.
-    $grants = array(':default' => $access);
-    $hook_implementations = \Drupal::moduleHandler()->getImplementations('entity_field_access');
-    foreach ($hook_implementations as $module) {
-      $grants = array_merge($grants, array($module => module_invoke($module, 'entity_field_access', $operation, $this, $account)));
-    }
-    // Also allow modules to alter the returned grants/denies.
-    $context = array(
-      'operation' => $operation,
-      'field' => $this,
-      'account' => $account,
-    );
-    drupal_alter('entity_field_access', $grants, $context);
-
-    // One grant being FALSE is enough to deny access immediately.
-    if (in_array(FALSE, $grants, TRUE)) {
-      return FALSE;
-    }
-    // At least one grant has the explicit opinion to allow access.
-    if (in_array(TRUE, $grants, TRUE)) {
-      return TRUE;
-    }
-    // All grants are NULL and have no opinion - deny access in that case.
-    return FALSE;
+    $access_controller = \Drupal::entityManager()->getAccessController($this->getParent()->entityType());
+    return $access_controller->fieldAccess($operation, $this->getFieldDefinition(), $account, $this);
   }
 
   /**
-   * Contains the default access logic of this field.
-   *
-   * See \Drupal\Core\TypedData\AccessibleInterface::access() for the parameter
-   * doucmentation. This method can be overriden by field sub classes to provide
-   * a different default access logic. That allows them to inherit the complete
-   * access() method which contains the access hook invocation logic.
-   *
-   * @return bool
-   *   TRUE if access to this field is allowed per default, FALSE otherwise.
+   * {@inheritdoc}
    */
   public function defaultAccess($operation = 'view', AccountInterface $account = NULL) {
     // Grant access per default.
diff --git a/core/lib/Drupal/Core/Entity/Field/FieldDefinition.php b/core/lib/Drupal/Core/Entity/Field/FieldDefinition.php
new file mode 100644
index 0000000..0891889
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Field/FieldDefinition.php
@@ -0,0 +1,207 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Field\FieldDefinition.
+ */
+
+namespace Drupal\Core\Entity\Field;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\TypedData\DataDefinition;
+
+/**
+ * A class for defining entity fields.
+ */
+class FieldDefinition implements FieldDefinitionInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldName() {
+    return $this->definition['field_name'];
+  }
+
+  /**
+   * Sets the field name.
+   *
+   * @param string $name
+   *   The field name to set.
+   *
+   * @return \Drupal\Core\TypedData\DataDefinition
+   *   The object itself for chaining.
+   */
+  public function setFieldName($name) {
+    $this->definition['field_name'] = $name;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldType() {
+    // Cut of the leading field_item: prefix from 'field_item:FIELD_TYPE'.
+    $parts = explode(':', $this->getItemDefinition()->getType());
+    return $parts[1];
+  }
+
+  /**
+   * Sets the field type.
+   *
+   * @param string $type
+   *   The field type to set.
+   *
+   * @return \Drupal\Core\TypedData\DataDefinition
+   *   The object itself for chaining.
+   */
+  public function setFieldType($type) {
+    $this->definition['type'] = 'entity_field';
+    $this->definition['item_definition']['type'] = 'field_item:' . $type;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldSettings() {
+    return $this->definition['item_definition']['settings'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldSetting($setting_name) {
+    return $this->definition['item_definition']['settings'][$setting_name];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldPropertyNames() {
+    return array_keys(\Drupal::typedData()->create($this->getItemDefinition())->getPropertyDefinitions());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isFieldTranslatable() {
+    return !empty($this->definition['translatable']);
+  }
+
+  /**
+   * Sets whether the field is translatable.
+   *
+   * @param boolean $translatable
+   *   Whether the field is translatable.
+   *
+   * @return \Drupal\Core\TypedData\DataDefinition
+   *   The object itself for chaining.
+   */
+  public function setTranslatable($translatable) {
+    $this->definition['translatable'] = $translatable;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldLabel() {
+    return $this->definition['label'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldDescription() {
+    return $this->definition['description'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldCardinality() {
+    // @todo: Support reading out a possible cardinality constraint?
+    return $this->isList() ? FIELD_CARDINALITY_UNLIMITED : 1;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isFieldRequired() {
+    return !empty($this->definition['required']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isFieldQueryable() {
+    return isset($this->definition['queryable']) ? $this->definition['queryable'] : !$this->isComputed();
+  }
+
+  /**
+   * Sets whether the field is queryable.
+   *
+   * @param boolean $queryable
+   *   Whether the field is queryable.
+   *
+   * @return \Drupal\Core\TypedData\DataDefinition
+   *   The object itself for chaining.
+   */
+  public function setQueryable($queryable) {
+    $this->definition['queryable'] = $queryable;
+    return $this;
+  }
+
+  /**
+   * Sets constraints for a given field item property.
+   *
+   * @param string $name
+   *   The name of the property to set constraints for.
+   * @param array $constraints
+   *   The constraints to set.
+   *
+   * @return \Drupal\Core\TypedData\DataDefinition
+   *   The object itself for chaining.
+   */
+  public function setPropertyConstraints($name, array $constraints) {
+    $this->definition['item_definition']['constraints']['ComplexData'][$name] = $constraints;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isFieldConfigurable() {
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldDefaultValue(EntityInterface $entity) {
+    return $this->getFieldSetting('default_value');
+  }
+
+  /**
+   * Allows creating field definition objects from old style definition arrays.
+   *
+   * @todo: Remove once no old-style definition arrays need to be supported.
+   */
+  public static function createFromOldStyleDefinition($field_name, array $definition) {
+    unset($definition['list']);
+
+    $list_definition = $definition;
+    $list_definition['type'] = 'entity_field';
+    unset($list_definition['constraints']);
+    unset($list_definition['settings']);
+    $new = new FieldDefinition($list_definition);
+
+    if (isset($definition['list_class'])) {
+      $new->setClass($definition['list_class']);
+    }
+    // Apply the rest to the item definition.
+    $new->setItemDefinition(new FieldDefinition($definition));
+    $new->setFieldName($field_name);
+    return $new;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Field/FieldDefinitionInterface.php b/core/lib/Drupal/Core/Entity/Field/FieldDefinitionInterface.php
index b8d1389..2314339 100644
--- a/core/lib/Drupal/Core/Entity/Field/FieldDefinitionInterface.php
+++ b/core/lib/Drupal/Core/Entity/Field/FieldDefinitionInterface.php
@@ -68,10 +68,9 @@ public function getFieldName();
    * Returns the field type.
    *
    * @return string
-   *   The field type.
+   *   The field type, i.e. the id of a field type plugin. For example 'text'.
    *
-   * @todo Provide more information about field types after
-   *   https://drupal.org/node/1969728 is implemented.
+   * @see \Drupal\Core\Entity\Field\FieldTypePluginManager
    */
   public function getFieldType();
 
@@ -123,6 +122,20 @@ public function getFieldPropertyNames();
   public function isFieldTranslatable();
 
   /**
+   * Determines whether the field is configurable via field.module.
+   *
+   * @return bool
+   */
+  public function isFieldConfigurable();
+
+  /**
+   * Determines whether the field is queryable via QueryInterface.
+   *
+   * @return bool
+   */
+  public function isFieldQueryable();
+
+  /**
    * Returns the human-readable label for the field.
    *
    * @return string
diff --git a/core/lib/Drupal/Core/Entity/Field/FieldInterface.php b/core/lib/Drupal/Core/Entity/Field/FieldInterface.php
index 4708418..e2ee161 100644
--- a/core/lib/Drupal/Core/Entity/Field/FieldInterface.php
+++ b/core/lib/Drupal/Core/Entity/Field/FieldInterface.php
@@ -60,6 +60,18 @@ public function getLangcode();
   public function getFieldDefinition();
 
   /**
+   * Contains the default access logic of this field.
+   *
+   * See \Drupal\Core\TypedData\AccessibleInterface::access() for the parameter
+   * doucmentation. This method can be overriden by field sub classes to provide
+   * a different default access logic.
+   *
+   * @return bool
+   *   TRUE if access to this field is allowed per default, FALSE otherwise.
+   */
+  public function defaultAccess($operation = 'view', AccountInterface $account = NULL);
+
+  /**
    * Filters out empty field items and re-numbers the item deltas.
    */
   public function filterEmptyValues();
diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/Field.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/Field.php
new file mode 100644
index 0000000..5200370
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/Field.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Plugin\DataType\Field.
+ */
+
+namespace Drupal\Core\Entity\Plugin\DataType;
+
+use Drupal\Core\TypedData\Annotation\DataType;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * Defines a data type plugin for entity fields, i.e. the list of field items.
+ *
+ * Note that the class only register the plugin, and is actually never used.
+ * @todo: Move the implementation to this place also.
+ *
+ * @DataType(
+ *   id = "entity_field",
+ *   label = @Translation("Entity field"),
+ *   class = "\Drupal\Core\Entity\Field\Field"
+ * )
+ */
+class Field extends \Drupal\Core\Entity\Field\Field {
+
+}
diff --git a/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php b/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php
index 3a77f73..ddecebf 100644
--- a/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php
+++ b/core/modules/edit/lib/Drupal/edit/Access/EditEntityFieldAccessCheck.php
@@ -71,8 +71,7 @@ public function access(Route $route, Request $request) {
    * {@inheritdoc}
    */
   public function accessEditEntityField(EntityInterface $entity, $field_name) {
-    $entity_type = $entity->entityType();
-    return $entity->access('update') && ($field = $this->fieldInfo->getField($entity_type, $field_name)) && field_access('edit', $field, $entity_type, $entity);
+    return $entity->access('update') && $entity->get($field_name)->access('edit');
   }
 
   /**
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceController.php b/core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceController.php
index 426af2c..b1dc3cd 100644
--- a/core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceController.php
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceController.php
@@ -12,6 +12,8 @@
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Controller\ControllerInterface;
+use Drupal\Core\Entity\EntityManager;
 
 /**
  * Defines route controller for entity reference.
@@ -26,13 +28,23 @@ class EntityReferenceController implements ContainerInjectionInterface {
   protected $entityReferenceAutocomplete;
 
   /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManager
+   */
+  protected $entityManager;
+
+  /**
    * Constructs a EntityReferenceController object.
    *
    * @param \Drupal\entity_reference\EntityReferenceAutocomplete $entity_reference_autcompletion
-   *   The autocompletion helper for entity references
+   *   The autocompletion helper for entity references.
+   * @param \Drupal\Core\Entity\EntityManager êntity_manager
+   *   The entity manager.
    */
-  public function __construct(EntityReferenceAutocomplete $entity_reference_autcompletion) {
+  public function __construct(EntityReferenceAutocomplete $entity_reference_autcompletion, EntityManager $entity_manager) {
     $this->entityReferenceAutocomplete = $entity_reference_autcompletion;
+    $this->entityManager = $entity_manager;
   }
 
   /**
@@ -40,7 +52,8 @@ public function __construct(EntityReferenceAutocomplete $entity_reference_autcom
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('entity_reference.autocomplete')
+      $container->get('entity_reference.autocomplete'),
+      $container->get('entity.manager')
     );
   }
 
@@ -77,7 +90,8 @@ public function handleAutocomplete(Request $request, $type, $field_name, $entity
       throw new AccessDeniedHttpException();
     }
 
-    if ($field['type'] != 'entity_reference' || !field_access('edit', $field, $entity_type)) {
+    $access_controller = $this->entityManager->getAccessController($entity_type);
+    if ($field['type'] != 'entity_reference' || !$access_controller->fieldAccess('edit', $instance)) {
       throw new AccessDeniedHttpException();
     }
 
diff --git a/core/modules/field/field.api.php b/core/modules/field/field.api.php
index 50b5edb..e0d2d13 100644
--- a/core/modules/field/field.api.php
+++ b/core/modules/field/field.api.php
@@ -573,32 +573,5 @@ function hook_field_purge_instance($instance) {
  */
 
 /**
- * Determine whether the user has access to a given field.
- *
- * This hook is invoked from field_access() to let modules block access to
- * operations on fields. If no module returns FALSE, the operation is allowed.
- *
- * @param $op
- *   The operation to be performed. Possible values: 'edit', 'view'.
- * @param \Drupal\field\FieldInterface $field
- *   The field on which the operation is to be performed.
- * @param $entity_type
- *   The type of $entity; for example, 'node' or 'user'.
- * @param $entity
- *   (optional) The entity for the operation.
- * @param $account
- *   (optional) The account to check; if not given use currently logged in user.
- *
- * @return
- *   TRUE if the operation is allowed, and FALSE if the operation is denied.
- */
-function hook_field_access($op, \Drupal\field\FieldInterface $field, $entity_type, $entity, $account) {
-  if ($field['field_name'] == 'field_of_interest' && $op == 'edit') {
-    return $account->hasPermission('edit field of interest');
-  }
-  return TRUE;
-}
-
-/**
  * @} End of "addtogroup hooks".
  */
diff --git a/core/modules/field/field.deprecated.inc b/core/modules/field/field.deprecated.inc
index e33b150..c77c294 100644
--- a/core/modules/field/field.deprecated.inc
+++ b/core/modules/field/field.deprecated.inc
@@ -829,3 +829,32 @@ function field_get_items(EntityInterface $entity, $field_name, $langcode = NULL)
 function field_get_default_value(EntityInterface $entity, $field, $instance, $langcode = NULL) {
   return $instance->getFieldDefaultValue($entity);
 }
+
+/**
+ * Determines whether the user has access to a given field.
+ *
+ * @param string $op
+ *   The operation to be performed. Possible values:
+ *   - edit
+ *   - view
+ * @param \Drupal\field\FieldInterface $field
+ *   The field on which the operation is to be performed.
+ * @param $entity_type
+ *   The type of $entity; for example, 'node' or 'user'.
+ * @param $entity
+ *   (optional) The entity for the operation.
+ * @param $account
+ *   (optional) The account to check, if not given use currently logged in user.
+ *
+ * @return
+ *   TRUE if the operation is allowed; FALSE if the operation is denied.
+ *
+ * @deprecated as of Drupal 8.0. Use
+ *   Drupal\Core\Entity\EntityAccessControllerInterface::fieldAccess()
+ */
+function field_access($op, FieldInterface $field, $entity_type, $entity = NULL, $account = NULL) {
+  $access_controller = \Drupal::entityManager()->getAccessController($entity_type);
+
+  $items = $entity ? $entity->get($field->id()) : NULL;
+  return $access_controller->fieldAccess($op, $field, $account, $items);
+}
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index 681fc63..7d0a597 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -719,42 +719,6 @@ function field_view_field(EntityInterface $entity, $field_name, $display_options
 }
 
 /**
- * Determines whether the user has access to a given field.
- *
- * @param string $op
- *   The operation to be performed. Possible values:
- *   - edit
- *   - view
- * @param \Drupal\field\FieldInterface $field
- *   The field on which the operation is to be performed.
- * @param $entity_type
- *   The type of $entity; for example, 'node' or 'user'.
- * @param $entity
- *   (optional) The entity for the operation.
- * @param $account
- *   (optional) The account to check, if not given use currently logged in user.
- *
- * @return
- *   TRUE if the operation is allowed; FALSE if the operation is denied.
- */
-function field_access($op, FieldInterface $field, $entity_type, $entity = NULL, $account = NULL) {
-  global $user;
-
-  if (!isset($account)) {
-    $account = $user;
-  }
-
-  foreach (\Drupal::moduleHandler()->getImplementations('field_access') as $module) {
-    $function = $module . '_field_access';
-    $access = $function($op, $field, $entity_type, $entity, $account);
-    if ($access === FALSE) {
-      return FALSE;
-    }
-  }
-  return TRUE;
-}
-
-/**
  * Extracts the bundle name from a bundle object.
  *
  * @param $entity_type
diff --git a/core/modules/field/lib/Drupal/field/Entity/Field.php b/core/modules/field/lib/Drupal/field/Entity/Field.php
index 3421461..31cea7b 100644
--- a/core/modules/field/lib/Drupal/field/Entity/Field.php
+++ b/core/modules/field/lib/Drupal/field/Entity/Field.php
@@ -718,4 +718,17 @@ public function __wakeup() {
     $this->__construct($values);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function isFieldConfigurable() {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isFieldQueryable() {
+    return TRUE;
+  }
 }
diff --git a/core/modules/field/lib/Drupal/field/Entity/FieldInstance.php b/core/modules/field/lib/Drupal/field/Entity/FieldInstance.php
index eaa0c70..c9f0c77 100644
--- a/core/modules/field/lib/Drupal/field/Entity/FieldInstance.php
+++ b/core/modules/field/lib/Drupal/field/Entity/FieldInstance.php
@@ -605,6 +605,20 @@ public function allowBundleRename() {
   /**
    * {@inheritdoc}
    */
+  public function isFieldConfigurable() {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isFieldQueryable() {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function offsetExists($offset) {
     return (isset($this->{$offset}) || $offset == 'field_id' || $offset == 'field_name');
   }
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php
index bb9edd3..780edeb 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php
@@ -84,7 +84,7 @@ public function view(FieldInterface $items) {
       $info = array(
         '#theme' => 'field',
         '#title' => $this->fieldDefinition->getFieldLabel(),
-        '#access' => $this->checkFieldAccess('view', $entity),
+        '#access' => $items->access('view'),
         '#label_display' => $this->label,
         '#view_mode' => $this->viewMode,
         '#language' => $items->getLangcode(),
@@ -124,22 +124,6 @@ public function settingsSummary() {
   public function prepareView(array $entities_items) { }
 
   /**
-   * Returns whether the currently logged in user has access to the field.
-   *
-   * @todo Remove this once Field API access is unified with entity field
-   *   access: http://drupal.org/node/1994140.
-   */
-  protected function checkFieldAccess($op, $entity) {
-    if ($this->fieldDefinition instanceof FieldInstanceInterface) {
-      $field = $this->fieldDefinition->getField();
-      return field_access($op, $field, $entity->entityType(), $entity);
-    }
-    else {
-      return FALSE;
-    }
-  }
-
-  /**
    * Returns the array of field settings.
    *
    * @return array
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php
index a84c94f..c81cf10 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php
@@ -128,7 +128,7 @@ public function form(FieldInterface $items, array &$form, array &$form_state, $g
             'field-widget-' . drupal_html_class($this->getPluginId()),
           ),
         ),
-        '#access' => $this->checkFieldAccess('edit', $items->getEntity()),
+        '#access' => $items->access('edit'),
         'widget' => $elements,
       ),
     );
@@ -427,22 +427,6 @@ protected function sortItems(FieldInterface $items) {
   }
 
   /**
-   * Returns whether the currently logged in user has access to the field.
-   *
-   * @todo Remove this once Field API access is unified with entity field
-   *   access: http://drupal.org/node/1994140.
-   */
-  protected function checkFieldAccess($op, $entity) {
-    if ($this->fieldDefinition instanceof FieldInstanceInterface) {
-      $field = $this->fieldDefinition->getField();
-      return field_access($op, $field, $entity->entityType(), $entity);
-    }
-    else {
-      return FALSE;
-    }
-  }
-
-  /**
    * Returns the array of field settings.
    *
    * @return array
diff --git a/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php b/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php
index ea74a94..8750cb6 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Language\Language;
+use Drupal\Core\Entity\EntityManager;
 use Drupal\field\Plugin\Type\Formatter\FormatterPluginManager;
 use Drupal\views\ViewExecutable;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
@@ -71,6 +72,13 @@ class Field extends FieldPluginBase {
   protected $formatterOptions;
 
   /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManager
+   */
+  protected $entityManager;
+
+  /**
    * The field formatter plugin manager.
    *
    * @var \Drupal\field\Plugin\Type\Formatter\FormatterPluginManager
@@ -86,12 +94,15 @@ class Field extends FieldPluginBase {
    *   The plugin_id for the plugin instance.
    * @param array $plugin_definition
    *   The plugin implementation definition.
+   * @param \Drupal\Core\Entity\EntityManager $entity_manager
+   *   The field formatter plugin manager.
    * @param \Drupal\field\Plugin\Type\Formatter\FormatterPluginManager $formatter_plugin_manager
    *   The field formatter plugin manager.
    */
-  public function __construct(array $configuration, $plugin_id, array $plugin_definition, FormatterPluginManager $formatter_plugin_manager) {
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityManager $entity_manager, FormatterPluginManager $formatter_plugin_manager) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
+    $this->entityManager = $entity_manager;
     $this->formatterPluginManager = $formatter_plugin_manager;
   }
 
@@ -103,6 +114,7 @@ public static function create(ContainerInterface $container, array $configuratio
       $configuration,
       $plugin_id,
       $plugin_definition,
+      $container->get('entity.manager'),
       $container->get('plugin.manager.field.formatter')
     );
   }
@@ -147,7 +159,8 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$o
    */
   public function access() {
     $base_table = $this->get_base_table();
-    return field_access('view', $this->field_info, $this->definition['entity_tables'][$base_table]);
+    $access_controller = $this->entityManager->getAccessController($this->definition['entity_tables'][$base_table]);
+    return $access_controller->fieldAccess('view', $this->field_info);
   }
 
   /**
diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldAccessTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldAccessTest.php
index 0ce394d..81eb005 100644
--- a/core/modules/field/lib/Drupal/field/Tests/FieldAccessTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/FieldAccessTest.php
@@ -81,7 +81,7 @@ function setUp() {
   }
 
   /**
-   * Test that hook_field_access() is called.
+   * Test that hook_entity_field_access() is called.
    */
   function testFieldAccess() {
 
@@ -90,7 +90,7 @@ function testFieldAccess() {
     $this->assertText($this->test_view_field_value);
 
     // Assert the text is not visible for anonymous users.
-    // The field_test module implements hook_field_access() which will
+    // The field_test module implements hook_entity_field_access() which will
     // specifically target the 'test_view_field' field.
     $this->drupalLogout();
     $this->drupalGet('node/' . $this->node->id());
diff --git a/core/modules/field/lib/Drupal/field/Tests/FormTest.php b/core/modules/field/lib/Drupal/field/Tests/FormTest.php
index 4919115..4090764 100644
--- a/core/modules/field/lib/Drupal/field/Tests/FormTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/FormTest.php
@@ -505,7 +505,8 @@ function testFieldFormAccess() {
       ->setComponent($field_name)
       ->save();
 
-    // Create a field with no edit access - see field_test_field_access().
+    // Create a field with no edit access. See
+    // field_test_entity_field_access().
     $field_no_access = array(
       'name' => 'field_no_edit_access',
       'entity_type' => $entity_type,
diff --git a/core/modules/field/tests/modules/field_test/field_test.field.inc b/core/modules/field/tests/modules/field_test/field_test.field.inc
index 3ac75fa..ffd926d 100644
--- a/core/modules/field/tests/modules/field_test/field_test.field.inc
+++ b/core/modules/field/tests/modules/field_test/field_test.field.inc
@@ -6,8 +6,10 @@
  */
 
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\Field\FieldDefinitionInterface;
+use Drupal\Core\Entity\Field\FieldInterface;
+use Drupal\Core\Session\AccountInterface;
 use Drupal\field\FieldException;
-use Drupal\field\FieldInterface;
 
 /**
  * Implements hook_field_info().
@@ -199,16 +201,16 @@ function field_test_default_value(EntityInterface $entity, $field, $instance) {
 }
 
 /**
- * Implements hook_field_access().
+ * Implements hook_entity_field_access().
  */
-function field_test_field_access($op, FieldInterface $field, $entity_type, $entity, $account) {
-  if ($field['field_name'] == "field_no_{$op}_access") {
+function field_test_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldInterface $field = NULL) {
+  if ($field_definition->getFieldName() == "field_no_{$operation}_access") {
     return FALSE;
   }
 
   // Only grant view access to test_view_field fields when the user has
   // 'view test_view_field content' permission.
-  if ($field['field_name'] == 'test_view_field' && $op == 'view' && !$account->hasPermission('view test_view_field content')) {
+  if ($field_definition->getFieldName() == 'test_view_field' && $operation == 'view' && !$account->hasPermission('view test_view_field content')) {
     return FALSE;
   }
 
diff --git a/core/modules/file/file.api.php b/core/modules/file/file.api.php
index 2624623..eaf8ba4 100644
--- a/core/modules/file/file.api.php
+++ b/core/modules/file/file.api.php
@@ -216,7 +216,7 @@ function hook_file_delete(Drupal\file\FileInterface $file) {
  *   that denial may be overridden by another entity controller, making this
  *   grant permissive rather than restrictive.
  *
- * @see hook_field_access().
+ * @see hook_entity_field_access().
  */
 function hook_file_download_access($field, Drupal\Core\Entity\EntityInterface $entity, Drupal\file\FileInterface $file) {
   if ($entity->entityType() == 'node') {
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index 769d480..ef77817 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -643,7 +643,7 @@ function file_file_download($uri, $field_type = 'file') {
       foreach ($entities as $entity) {
         $field = field_info_field($entity_type, $field_name);
         // Check if access to this field is not disallowed.
-        if (!field_access('view', $field, $entity_type, $entity)) {
+        if (!$entity->get($field_name)->access('view')) {
           $denied = TRUE;
           continue;
         }
diff --git a/core/modules/file/lib/Drupal/file/Tests/FilePrivateTest.php b/core/modules/file/lib/Drupal/file/Tests/FilePrivateTest.php
index afbeeba..e51ba64 100644
--- a/core/modules/file/lib/Drupal/file/Tests/FilePrivateTest.php
+++ b/core/modules/file/lib/Drupal/file/Tests/FilePrivateTest.php
@@ -43,7 +43,8 @@ function testPrivateFile() {
     $field_name = strtolower($this->randomName());
     $this->createFileField($field_name, 'node', $type_name, array('uri_scheme' => 'private'));
 
-    // Create a field with no view access - see field_test_field_access().
+    // Create a field with no view access. See
+    // field_test_entity_field_access().
     $no_access_field_name = 'field_no_view_access';
     $this->createFileField($no_access_field_name, 'node', $type_name, array('uri_scheme' => 'private'));
 
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 1c81419..7b49343 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.module
+++ b/core/modules/system/tests/modules/entity_test/entity_test.module
@@ -6,6 +6,9 @@
  */
 
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\Field\FieldDefinitionInterface;
+use Drupal\Core\Entity\Field\FieldInterface;
+use Drupal\Core\Session\AccountInterface;
 use Drupal\entity\Entity\EntityFormDisplay;
 use Drupal\field\Entity\Field;
 use Drupal\field\Entity\FieldInstance;
@@ -369,13 +372,15 @@ function entity_test_label_callback($entity_type, $entity, $langcode = NULL) {
  *
  * @see \Drupal\system\Tests\Entity\FieldAccessTest::testFieldAccess()
  */
-function entity_test_entity_field_access($operation, $field, $account) {
-  if ($field->getName() == 'field_test_text') {
-    if ($field->value == 'no access value') {
-      return FALSE;
-    }
-    elseif ($operation == 'delete' && $field->value == 'no delete access value') {
-      return FALSE;
+function entity_test_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldInterface $field = NULL) {
+  if ($field_definition->getFieldName() == 'field_test_text') {
+    if ($field) {
+      if ($field->value == 'no access value') {
+        return FALSE;
+      }
+      elseif ($operation == 'delete' && $field->value == 'no delete access value') {
+        return FALSE;
+      }
     }
   }
 }
