diff --git a/core/lib/Drupal/Core/Entity/EntityAccessController.php b/core/lib/Drupal/Core/Entity/EntityAccessController.php
index 5583aec..a811110 100644
--- a/core/lib/Drupal/Core/Entity/EntityAccessController.php
+++ b/core/lib/Drupal/Core/Entity/EntityAccessController.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\Core\Entity;
 
+use Drupal\Core\Entity\Field\FieldItemListInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Entity\Field\FieldDefinitionInterface;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Session\AccountInterface;
 
@@ -268,4 +270,42 @@ public function setModuleHandler(ModuleHandlerInterface $module_handler) {
     return $this;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function fieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account = NULL, FieldItemListInterface $items = NULL) {
+    $account = $this->prepareUser($account);
+
+    // Get the default access restriction that lives within this field.
+    $default = $items ? $items->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 = $this->moduleHandler->getImplementations('entity_field_access');
+    foreach ($hook_implementations as $module) {
+      $grants = array_merge($grants, array($module => $this->moduleHandler->invoke($module, 'entity_field_access', array($operation, $field_definition, $account, $items))));
+    }
+
+    // Also allow modules to alter the returned grants/denies.
+    $context = array(
+      'operation' => $operation,
+      'field_definition' => $field_definition,
+      'items' => $items,
+      'account' => $account,
+    );
+    $this->moduleHandler->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..3f3aac9 100644
--- a/core/lib/Drupal/Core/Entity/EntityAccessControllerInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityAccessControllerInterface.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\Core\Entity;
 
+use Drupal\Core\Entity\Field\FieldItemListInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Entity\Field\FieldDefinitionInterface;
 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 "update".
+   * @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\FieldItemListInterface $items
+   *   (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, FieldItemListInterface $items = NULL);
+
 }
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..dbc944e
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Field/FieldDefinition.php
@@ -0,0 +1,234 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Field\FieldDefinition.
+ */
+
+namespace Drupal\Core\Entity\Field;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * A class for defining entity fields.
+ */
+class FieldDefinition implements FieldDefinitionInterface {
+
+  /**
+   * The array holding values for all definition keys.
+   *
+   * @var array
+   */
+  protected $definition = array();
+
+  /**
+   * Constructs a new FieldDefinition object.
+   *
+   * @param array $definition
+   *   (optional) If given, a definition represented as array.
+   */
+  public function __construct(array $definition = array()) {
+    $this->definition = $definition;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldName() {
+    return $this->definition['field_name'];
+  }
+
+  /**
+   * Sets the field name.
+   *
+   * @param string $name
+   *   The field name to set.
+   *
+   * @return \Drupal\Core\Entity\Field\FieldDefinition
+   *   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->definition['type']);
+    return $parts[1];
+  }
+
+  /**
+   * Sets the field type.
+   *
+   * @param string $type
+   *   The field type to set.
+   *
+   * @return \Drupal\Core\Entity\Field\FieldDefinition
+   *   The object itself for chaining.
+   */
+  public function setFieldType($type) {
+    $this->definition['type'] = 'field_item:' . $type;
+    return $this;
+  }
+
+  /**
+   * Sets a field setting.
+   *
+   * @param string $type
+   *   The field type to set.
+   *
+   * @return \Drupal\Core\Entity\Field\FieldDefinition
+   *   The object itself for chaining.
+   */
+  public function setFieldSetting($setting_name, $value) {
+    $this->definition['settings'][$setting_name] = $value;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldSettings() {
+    return $this->definition['settings'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldSetting($setting_name) {
+    return isset($this->definition['settings'][$setting_name]) ? $this->definition['settings'][$setting_name] : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldPropertyNames() {
+    return array_keys(\Drupal::typedData()->create($this->definition['type'])->getPropertyDefinitions());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isFieldTranslatable() {
+    return !empty($this->definition['translatable']);
+  }
+
+  /**
+   * Sets whether the field is translatable.
+   *
+   * @param bool $translatable
+   *   Whether the field is translatable.
+   *
+   * @return \Drupal\Core\Entity\Field\FieldDefinition
+   *   The object itself for chaining.
+   */
+  public function setTranslatable($translatable) {
+    $this->definition['translatable'] = $translatable;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldLabel() {
+    return $this->definition['label'];
+  }
+
+  /**
+   * Sets the field label.
+   *
+   * @param string $label
+   *   The field label to set.
+   *
+   * @return \Drupal\Core\Entity\Field\FieldDefinition
+   *   The object itself for chaining.
+   */
+  public function setFieldLabel($label) {
+    $this->definition['label'] = $label;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldDescription() {
+    return $this->definition['description'];
+  }
+
+  /**
+   * Sets the field label.
+   *
+   * @param string $description
+   *   The field label to set.
+   *
+   * @return \Drupal\Core\Entity\Field\FieldDefinition
+   *   The object itself for chaining.
+   */
+  public function setFieldDescription($description) {
+    $this->definition['description'] = $description;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldCardinality() {
+    // @todo: Allow to control this.
+    return isset($this->definition['cardinality']) ? $this->definition['cardinality'] : 1;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isFieldRequired() {
+    return !empty($this->definition['required']);
+  }
+
+  /**
+   * Sets whether the field is required.
+   *
+   * @param bool $required
+   *   TRUE if the field is required, FALSE otherwise.
+   *
+   * @return \Drupal\Core\Entity\Field\FieldDefinition
+   *   The object itself for chaining.
+   */
+  public function setFieldRequired($required) {
+    return $this->definition['required'] = $required;
+    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\Entity\Field\FieldDefinition
+   *   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');
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Field/FieldDefinitionInterface.php b/core/lib/Drupal/Core/Entity/Field/FieldDefinitionInterface.php
index b93cf3a..b8dc354 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,13 @@ public function getFieldPropertyNames();
   public function isFieldTranslatable();
 
   /**
+   * Determines whether the field is configurable via field.module.
+   *
+   * @return bool
+   */
+  public function isFieldConfigurable();
+
+  /**
    * Returns the human-readable label for the field.
    *
    * @return string
diff --git a/core/lib/Drupal/Core/Entity/Field/FieldItemList.php b/core/lib/Drupal/Core/Entity/Field/FieldItemList.php
index 53d10b9..4b4e1eb 100644
--- a/core/lib/Drupal/Core/Entity/Field/FieldItemList.php
+++ b/core/lib/Drupal/Core/Entity/Field/FieldItemList.php
@@ -47,6 +47,7 @@ class FieldItemList extends ItemList implements FieldItemListInterface {
    */
   public function __construct(array $definition, $name = NULL, TypedDataInterface $parent = NULL) {
     parent::__construct($definition, $name, $parent);
+    $this->definition['field_name'] = $name;
     // Always initialize one empty item as most times a value for at least one
     // item will be present. That way prototypes created by
     // \Drupal\Core\TypedData\TypedDataManager::getPropertyInstance() will
@@ -79,8 +80,7 @@ public function getLangcode() {
    * {@inheritdoc}
    */
   public function getFieldDefinition() {
-    // @todo https://drupal.org/node/1988612
-    return NULL;
+    return new FieldDefinition($this->definition);
   }
 
   /**
@@ -195,52 +195,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/FieldItemListInterface.php b/core/lib/Drupal/Core/Entity/Field/FieldItemListInterface.php
index f5bfc22..300de8f 100644
--- a/core/lib/Drupal/Core/Entity/Field/FieldItemListInterface.php
+++ b/core/lib/Drupal/Core/Entity/Field/FieldItemListInterface.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Entity\Field;
 
+use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\TypedData\AccessibleInterface;
 use Drupal\Core\TypedData\ListInterface;
 
@@ -60,6 +61,17 @@ public function getLangcode();
   public function getFieldDefinition();
 
   /**
+   * Contains the default access logic of this field.
+   *
+   * See \Drupal\Core\Entity\EntityAccessControllerInterface::fieldAccess() for
+   * the parameter documentation.
+   *
+   * @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/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 6ecf645..161c230 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 01c9181..552d6f4 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -593,42 +593,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..1b763b3 100644
--- a/core/modules/field/lib/Drupal/field/Entity/Field.php
+++ b/core/modules/field/lib/Drupal/field/Entity/Field.php
@@ -718,4 +718,11 @@ public function __wakeup() {
     $this->__construct($values);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function isFieldConfigurable() {
+    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 8f391ad..412bae2 100644
--- a/core/modules/field/lib/Drupal/field/Entity/FieldInstance.php
+++ b/core/modules/field/lib/Drupal/field/Entity/FieldInstance.php
@@ -610,6 +610,13 @@ public function allowBundleRename() {
   /**
    * {@inheritdoc}
    */
+  public function isFieldConfigurable() {
+    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 82bea1b..dae5ce2 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(FieldItemListInterface $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(),
@@ -137,22 +137,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 bb64e0b..9112ebc 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(FieldItemListInterface $items, array &$form, array &$form_s
             '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(FieldItemListInterface $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..5d79d05 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\FieldItemListInterface;
+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, FieldItemListInterface $items = 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 5dfdb30..3dcbcef 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -649,7 +649,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/entity.api.php b/core/modules/system/entity.api.php
index 342f2e6..6558afc 100644
--- a/core/modules/system/entity.api.php
+++ b/core/modules/system/entity.api.php
@@ -705,23 +705,26 @@ function hook_entity_operation_alter(array &$operations, \Drupal\Core\Entity\Ent
  * Control access to fields.
  *
  * This hook is invoked from
- * \Drupal\Core\Entity\Field\FieldItemListInterface::access() to let modules
+ * \Drupal\Core\Entity\EntityAccessController::fieldAccess() to let modules
  * grant or deny operations on fields.
  *
  * @param string $operation
  *   The operation to be performed. See
  *   \Drupal\Core\TypedData\AccessibleInterface::access() for possible values.
- * @param \Drupal\Core\Entity\Field\FieldItemListInterface $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\FieldItemListInterface $items
+ *   (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\FieldItemListInterface $items = NULL) {
+  if ($field_definition->getFieldName() == 'field_of_interest' && $operation == 'update') {
     return user_access('update field of interest', $account);
   }
 }
@@ -739,13 +742,16 @@ 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\FieldItemList).
+ *   - field_definition: The field definition object
+ *     (\Drupal\Core\Entity\Field\FieldDefinitionInterface)
  *   - account: The user account to check access for
  *     (Drupal\user\Entity\User).
+ *   - items: (optional) The entity field items
+ *     (\Drupal\Core\Entity\Field\FieldItemListInterface).
  */
 function hook_entity_field_access_alter(array &$grants, array $context) {
-  $field = $context['field'];
-  if ($field->getName() == 'field_of_interest' && $grants['node'] === FALSE) {
+  $field_definition = $context['field_definition'];
+  if ($field_definition->getFieldName() == 'field_of_interest' && $grants['node'] === FALSE) {
     // Override node module's restriction to no opinion. We don't want to
     // provide our own access hook, we only want to take out node module's part
     // in the access handling of this field. We also don't want to switch node
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 6e1153f..fabd1d3 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,8 @@
  */
 
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\Field\FieldDefinitionInterface;
+use Drupal\Core\Entity\Field\FieldItemListInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\entity\Entity\EntityFormDisplay;
 use Drupal\field\Entity\Field;
@@ -406,13 +408,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, FieldItemListInterface $items = NULL) {
+  if ($field_definition->getFieldName() == 'field_test_text') {
+    if ($items) {
+      if ($items[0]->value == 'no access value') {
+        return FALSE;
+      }
+      elseif ($operation == 'delete' && $items[0]->value == 'no delete access value') {
+        return FALSE;
+      }
     }
   }
 }
@@ -423,8 +427,7 @@ function entity_test_entity_field_access($operation, $field, $account) {
  * @see \Drupal\system\Tests\Entity\FieldAccessTest::testFieldAccess()
  */
 function entity_test_entity_field_access_alter(array &$grants, array $context) {
-  $field = $context['field'];
-  if ($field->getName() == 'field_test_text' && $field->value == 'access alter value') {
+  if ($context['field_definition']->getFieldName() == 'field_test_text' && $context['items'][0]->value == 'access alter value') {
     $grants[':default'] = FALSE;
   }
 }
diff --git a/core/tests/Drupal/Tests/Core/Entity/FieldDefinitionTest.php b/core/tests/Drupal/Tests/Core/Entity/FieldDefinitionTest.php
new file mode 100644
index 0000000..23f5a64
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Entity/FieldDefinitionTest.php
@@ -0,0 +1,135 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Entity\FieldDefinitionTest.
+ */
+
+namespace Drupal\Tests\Core\Entity;
+
+use Drupal\Core\Entity\Field\FieldDefinition;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests \Drupal\Core\Entity\Field\FieldDefinition.
+ *
+ * @group Entity
+ */
+class FieldDefinitionTest extends UnitTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Field definition test',
+      'description' => 'Unit test for FieldDefinition.',
+      'group' => 'Entity'
+    );
+  }
+
+  /**
+   * Tests field name methods.
+   */
+  public function testFieldName() {
+    $definition = new FieldDefinition();
+    $field_name = $this->randomName();
+    $definition->setFieldName($field_name);
+    $this->assertEquals($field_name, $definition->getFieldName());
+  }
+
+  /**
+   * Tests field label methods.
+   */
+  public function testFieldLabel() {
+    $definition = new FieldDefinition();
+    $label = $this->randomName();
+    $definition->setFieldLabel($label);
+    $this->assertEquals($label, $definition->getFieldLabel());
+  }
+
+  /**
+   * Tests field description methods.
+   */
+  public function testFieldDescription() {
+    $definition = new FieldDefinition();
+    $description = $this->randomName();
+    $definition->setFieldDescription($description);
+    $this->assertEquals($description, $definition->getFieldDescription());
+  }
+
+  /**
+   * Tests field type methods.
+   */
+  public function testFieldType() {
+    $definition = new FieldDefinition();
+    $field_name = $this->randomName();
+    $definition->setFieldType($field_name);
+    $this->assertEquals($field_name, $definition->getFieldType());
+  }
+
+  /**
+   * Tests field settings methods.
+   */
+  public function testFieldSettings() {
+    $definition = new FieldDefinition();
+    $setting = $this->randomName();
+    $value = $this->randomName();
+    $definition->setFieldSetting($setting, $value);
+    $this->assertEquals($value, $definition->getFieldSetting($setting));
+    $this->assertEquals(array($setting => $value), $definition->getFieldSettings());
+  }
+
+  /**
+   * Tests field default value.
+   */
+  public function testFieldDefaultValue() {
+    $definition = new FieldDefinition();
+    $setting = 'default_value';
+    $value = $this->randomName();
+    $definition->setFieldSetting($setting, $value);
+    $entity = $this->getMockBuilder('Drupal\Core\Entity\Entity')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $this->assertEquals($value, $definition->getFieldDefaultValue($entity));
+  }
+
+  /**
+   * Tests field translatable methods.
+   */
+  public function testFieldTranslatable() {
+    $definition = new FieldDefinition();
+    $this->assertFalse($definition->isFieldTranslatable());
+    $definition->setTranslatable(TRUE);
+    $this->assertTrue($definition->isFieldTranslatable());
+    $definition->setTranslatable(FALSE);
+    $this->assertFalse($definition->isFieldTranslatable());
+  }
+
+  /**
+   * Tests field cardinality.
+   */
+  public function testFieldCardinality() {
+    $definition = new FieldDefinition();
+    $this->assertEquals(1, $definition->getFieldCardinality());
+    // @todo: Add more tests when this can be controlled.
+  }
+
+  /**
+   * Tests required.
+   */
+  public function testFieldRequired() {
+    $definition = new FieldDefinition();
+    $this->assertFalse($definition->isFieldRequired());
+    $definition->setFieldRequired(TRUE);
+    $this->assertTrue($definition->isFieldRequired());
+    $definition->setFieldRequired(FALSE);
+    $this->assertFalse($definition->isFieldRequired());
+  }
+
+  /**
+   * Tests configurable.
+   */
+  public function testFieldConfigurable() {
+    $definition = new FieldDefinition();
+    $this->assertFalse($definition->isFieldConfigurable());
+  }
+
+}
