diff --git a/core/lib/Drupal/Core/Entity/Display/EntityViewDisplayInterface.php b/core/lib/Drupal/Core/Entity/Display/EntityViewDisplayInterface.php
index 8a9ce80..5fefc54 100644
--- a/core/lib/Drupal/Core/Entity/Display/EntityViewDisplayInterface.php
+++ b/core/lib/Drupal/Core/Entity/Display/EntityViewDisplayInterface.php
@@ -7,9 +7,47 @@
 
 namespace Drupal\Core\Entity\Display;
 
+use Drupal\Core\Entity\EntityInterface;
+
 /**
  * Provides a common interface for entity view displays.
  */
 interface EntityViewDisplayInterface extends EntityDisplayInterface {
 
+  /**
+   * Prepares field data prior to display.
+   *
+   * This method lets field formatters load additional data needed for display
+   * that is not automatically loaded during entity loading. It accepts an array
+   * of entities to allow query optimization when displaying lists of entities.
+   *
+   * prepareFields() and attachFields() are two halves of the same operation. It
+   * is safe to call prepareFields() multiple times on the same entity before
+   * calling attachFields() on it, but calling any Field API operation on an
+   * entity between passing that entity to these two methods may yield incorrect
+   * results.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface[] $entities
+   *   An array of entities, keyed by entity ID.
+   */
+  public function prepareFields(array $entities);
+
+  /**
+   * Returns a renderable array for the fields on an entity.
+   *
+   * prepareFields() and attachFields() are two halves of the same operation. It
+   * is safe to call prepareFields() multiple times on the same entity before
+   * calling attachFields() on it, but calling any Field API operation on an
+   * entity between passing that entity to these two methods may yield incorrect
+   * results.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity..
+   *
+   * @return array
+   *   A renderable array for the field values.
+   */
+  public function attachFields(EntityInterface $entity);
+
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
index 6442577..28991e5 100644
--- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
+++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
@@ -87,33 +87,25 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
    * {@inheritdoc}
    */
   public function buildContent(array $entities, array $displays, $view_mode, $langcode = NULL) {
-    field_attach_prepare_view($this->entityType, $entities, $displays, $langcode);
-
-    // Initialize the field item attributes for the fields set to be displayed.
-    foreach ($entities as $entity) {
-      // The entity can include fields that aren't displayed, and the display
-      // can include components that aren't fields, so we want to iterate the
-      // intersection of $entity->getProperties() and $display->getComponents().
-      // However, the entity can have many more fields than are displayed, so we
-      // avoid the cost of calling $entity->getProperties() by iterating the
-      // intersection as follows.
-      foreach ($displays[$entity->bundle()]->getComponents() as $name => $options) {
-        if ($entity->hasField($name)) {
-          foreach ($entity->get($name) as $item) {
-            $item->_attributes = array();
-          }
-        }
-      }
-    }
-
-    module_invoke_all('entity_prepare_view', $this->entityType, $entities, $displays, $view_mode);
-
-    foreach ($entities as $entity) {
+    // Let the formatters prepare the field values.
+    $entities_by_bundle = array();
+    foreach ($entities as $id => $entity) {
       // Remove previously built content, if exists.
       $entity->content = array(
         '#view_mode' => $view_mode,
       );
-      $entity->content += field_attach_view($entity, $displays[$entity->bundle()], $langcode);
+      $entities_by_bundle[$entity->bundle()][$id] = $entity;
+    }
+    foreach ($entities_by_bundle as $bundle => $bundle_entities) {
+      $displays[$bundle]->prepareFields($bundle_entities);
+    }
+
+    // Invoke hook_entity_prepare_view().
+    module_invoke_all('entity_prepare_view', $this->entityType, $entities, $displays, $view_mode);
+
+    // Build the field formatters.
+    foreach ($entities as $entity) {
+      $entity->content += $displays[$entity->bundle()]->attachFields($entity);
     }
   }
 
diff --git a/core/lib/Drupal/Core/Field/Annotation/FieldFormatter.php b/core/lib/Drupal/Core/Field/Annotation/FieldFormatter.php
index 1ef899e..1887306 100644
--- a/core/lib/Drupal/Core/Field/Annotation/FieldFormatter.php
+++ b/core/lib/Drupal/Core/Field/Annotation/FieldFormatter.php
@@ -12,9 +12,8 @@
 /**
  * Defines a FieldFormatter annotation object.
  *
- * Formatters handle the display of field values. Formatter hooks are typically
- * called by the Field Attach API field_attach_prepare_view() and
- * field_attach_view() functions.
+ * Formatters handle the display of field values. They are typically
+ * instanciated and invoked by an EntityDisplay object.
  *
  * Additional annotation keys for formatters can be defined in
  * hook_field_formatter_info_alter().
diff --git a/core/lib/Drupal/Core/Field/FormatterInterface.php b/core/lib/Drupal/Core/Field/FormatterInterface.php
index ef6776d..fa63372 100644
--- a/core/lib/Drupal/Core/Field/FormatterInterface.php
+++ b/core/lib/Drupal/Core/Field/FormatterInterface.php
@@ -48,22 +48,20 @@ public function settingsSummary();
    * field that displays properties of the referenced entities such as name or
    * type.
    *
-   * This method operates on multiple entities. The $entities and $items
-   * parameters are arrays keyed by entity ID. For performance reasons,
-   * information for all involved entities should be loaded in a single query
-   * where possible.
+   * This method operates on multiple entities. The $entities_items parameter
+   * is an array keyed by entity ID. For performance reasons, information for
+   * all involved entities should be loaded in a single query where possible.
    *
-   * Changes or additions to field values are done by alterings the $items
-   * parameter by reference.
+   * Changes or additions to field values are done by directly altering the
+   * items.
    *
-   * @param array $entities_items
-   *   Array of field values (Drupal\Core\Field\FieldItemListInterface),
-   *   keyed by entity ID.
+   * @param \Drupal\Core\Field\FieldItemListInterface[] $entities_items
+   *   Array of field values, keyed by entity ID.
    */
   public function prepareView(array $entities_items);
 
   /**
-   * Builds a renderable array for one field on one entity instance.
+   * Builds a renderable array for a fully themed field.
    *
    * @param \Drupal\Core\Field\FieldItemListInterface $items
    *   The field values to be rendered.
@@ -81,7 +79,7 @@ public function view(FieldItemListInterface $items);
    *
    * @return array
    *   A renderable array for $items, as an array of child elements keyed by
-   *   numeric indexes starting from 0.
+   *   consecutive numeric indexes starting from 0.
    */
   public function viewElements(FieldItemListInterface $items);
 
diff --git a/core/modules/entity/lib/Drupal/entity/Entity/EntityDisplay.php b/core/modules/entity/lib/Drupal/entity/Entity/EntityDisplay.php
index baf1cbd..47ba000 100644
--- a/core/modules/entity/lib/Drupal/entity/Entity/EntityDisplay.php
+++ b/core/modules/entity/lib/Drupal/entity/Entity/EntityDisplay.php
@@ -8,6 +8,7 @@
 namespace Drupal\entity\Entity;
 
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\entity\EntityDisplayBase;
 
 /**
@@ -71,4 +72,57 @@ public function getRenderer($field_name) {
     return $formatter;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareFields(array $entities) {
+    if ($entities) {
+      // For each field in the bundle, group items across all entities and
+      // pass them to the formatter's prepareView() method.
+      $field_definitions = current($entities)->getPropertyDefinitions();
+      foreach ($field_definitions as $field_name => $definition) {
+        if ($formatter = $this->getRenderer($field_name)) {
+          $entities_items = array();
+          foreach ($entities as $id => $entity) {
+            $items = $entity->get($field_name);
+            $items->filterEmptyValues();
+
+            // Initialize the field item attributes for the fields set to be displayed.
+            foreach ($items as $item) {
+              $item->_attributes = array();
+            }
+            $entities_items[$id] = $items;
+          }
+
+          $formatter->prepareView($entities_items);
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function attachFields(EntityInterface $entity) {
+    $build = array();
+    foreach ($entity->getPropertyDefinitions() as $field_name => $definition) {
+      if ($formatter = $this->getRenderer($field_name)) {
+        $items = $entity->get($field_name);
+        $items->filterEmptyValues();
+        $build += $formatter->view($items);
+      }
+    }
+
+    // Let other modules alter the renderable array.
+    $context = array(
+      'entity' => $entity,
+      'view_mode' => $this->originalMode,
+      'display' => $this,
+    );
+    // @todo Better name for the hook ?
+    \Drupal::moduleHandler()->alter('field_attach_view', $build, $context);
+
+    return $build;
+  }
+
 }
diff --git a/core/modules/field/field.api.php b/core/modules/field/field.api.php
index 3f59be4..12432e7 100644
--- a/core/modules/field/field.api.php
+++ b/core/modules/field/field.api.php
@@ -366,18 +366,9 @@ function hook_field_attach_extract_form_values(\Drupal\Core\Entity\EntityInterfa
  *   The structured content array tree for all of the entity's fields.
  * @param $context
  *   An associative array containing:
- *   - entity: The entity with fields to render.
- *   - view_mode: View mode; for example, 'full' or 'teaser'.
- *   - display_options: Either a view mode string or an array of display
- *     options. If this hook is being invoked from field_attach_view(), the
- *     'display_options' element is set to the view mode string. If this hook
- *     is being invoked from field_view_field(), this element is set to the
- *     $display_options argument and the view_mode element is set to '_custom'.
- *     See field_view_field() for more information on what its $display_options
- *     argument contains.
- *   - langcode: The language code used for rendering.
- *
- * @deprecated as of Drupal 8.0. Use the entity system instead.
+ *   - entity: The entity being rendered.
+ *   - view_mode: The view mode; for example, 'full' or 'teaser'.
+ *   - display: The EntityDisplay holding the display options.
  */
 function hook_field_attach_view_alter(&$output, $context) {
   // Append RDF term mappings on displayed taxonomy links.
diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc
index f800519..a6adde7 100644
--- a/core/modules/field/field.attach.inc
+++ b/core/modules/field/field.attach.inc
@@ -120,96 +120,6 @@ function field_invoke_method($method, $target_function, EntityInterface $entity,
 }
 
 /**
- * Invokes a method across fields on multiple entities.
- *
- * @param string $method
- *   The name of the method to invoke.
- * @param callable $target_function
- *   A function that receives a FieldDefinitionInterface object and a bundle
- *   name and returns the object on which the method should be invoked.
- * @param array $entities
- *   An array of entities, keyed by entity ID.
- * @param mixed $a
- *   (optional) A parameter for the invoked method. Defaults to NULL.
- * @param mixed $b
- *   (optional) A parameter for the invoked method. Defaults to NULL.
- * @param $options
- *   (optional) An associative array of additional options, with the following
- *   keys:
- *   - field_name: The name of the field whose operation should be invoked. By
- *     default, the operation is invoked on all the fields in the entity's
- *     bundle.
- *
- * @return array
- *   An array of returned values keyed by entity ID.
- *
- * @see field_invoke_method()
- */
-function field_invoke_method_multiple($method, $target_function, array $entities, &$a = NULL, &$b = NULL, array $options = array()) {
-  $grouped_items = array();
-  $grouped_targets = array();
-  $return = array();
-
-  // Go through the entities and collect the instances on which the method
-  // should be called.
-  foreach ($entities as $entity) {
-    $entity_type = $entity->entityType();
-    $bundle = $entity->bundle();
-    $id = $entity->id();
-
-    // Determine the list of fields to iterate on.
-    $field_definitions = _field_invoke_get_field_definitions($entity_type, $bundle, $options);
-
-    foreach ($field_definitions as $field_definition) {
-      $field_name = $field_definition->getName();
-      $group_key = "$bundle:$field_name";
-
-      // Let the closure determine the target object on which the method should
-      // be called.
-      if (empty($grouped_targets[$group_key])) {
-        $target = call_user_func($target_function, $field_definition, $bundle);
-        if (method_exists($target, $method)) {
-          $grouped_targets[$group_key] = $target;
-        }
-        else {
-          $grouped_targets[$group_key] = FALSE;
-        }
-      }
-
-      // If there is a target, group the field items.
-      if ($grouped_targets[$group_key]) {
-        $items = $entity->get($field_name);
-        $items->filterEmptyValues();
-        $grouped_items[$group_key][$id] = $items;
-      }
-    }
-    // Initialize the return value for each entity.
-    $return[$id] = array();
-  }
-
-  // For each field, invoke the method and collect results.
-  foreach ($grouped_items as $key => $entities_items) {
-    $results = $grouped_targets[$key]->$method($entities_items, $a, $b);
-
-    if (isset($results)) {
-      // Collect results by entity.
-      // For hooks with array results, we merge results together.
-      // For hooks with scalar results, we collect results in an array.
-      foreach ($results as $id => $result) {
-        if (is_array($result)) {
-          $return[$id] = array_merge($return[$id], $result);
-        }
-        else {
-          $return[$id][] = $result;
-        }
-      }
-    }
-  }
-
-  return $return;
-}
-
-/**
  * Retrieves a list of field definitions to operate on.
  *
  * Helper for field_invoke_method().
diff --git a/core/modules/field/field.deprecated.inc b/core/modules/field/field.deprecated.inc
index 02f60e3..6cca8b9 100644
--- a/core/modules/field/field.deprecated.inc
+++ b/core/modules/field/field.deprecated.inc
@@ -389,109 +389,22 @@ function field_attach_extract_form_values(EntityInterface $entity, $form, &$form
 }
 
 /**
- * Prepares field data prior to display.
- *
- * This function lets field types and formatters load additional data needed for
- * display that is not automatically loaded during entity loading. It accepts an
- * array of entities to allow query optimization when displaying lists of
- * entities.
- *
- * field_attach_prepare_view() and field_attach_view() are two halves of the
- * same operation. It is safe to call field_attach_prepare_view() multiple times
- * on the same entity before calling field_attach_view() on it, but calling any
- * Field API operation on an entity between passing that entity to these two
- * functions may yield incorrect results.
- *
- * @param $entity_type
- *   The type of entities in $entities; e.g. 'node' or 'user'.
- * @param array $entities
- *   An array of entities, keyed by entity ID.
- * @param array $displays
- *   An array of entity display objects, keyed by bundle name.
- * @param $langcode
- *   (Optional) The language the field values are to be shown in. If no language
- *   is provided the current language is used.
- *
- * @deprecated as of Drupal 8.0. Use the entity system instead.
+ * @todo Remove when all calls are converted.
  */
 function field_attach_prepare_view($entity_type, array $entities, array $displays, $langcode = NULL) {
-  // To ensure hooks are only run once per entity, only process items without
-  // the _field_view_prepared flag.
-  // @todo: resolve this more generally for both entity and field level hooks.
-  $prepare = array();
+  // Group entities by bundles.
+  $entities_by_bundle = array();
   foreach ($entities as $id => $entity) {
-    if (empty($entity->_field_view_prepared)) {
-      // Add this entity to the items to be prepared.
-      $prepare[$id] = $entity;
-
-      // Mark this item as prepared.
-      $entity->_field_view_prepared = TRUE;
-    }
+    $entities_by_bundle[$entity->bundle()][$id] = $entity;
+  }
+  foreach ($entities_by_bundle as $bundle => $bundle_entities) {
+    $displays[$bundle]->prepareFields($bundle_entities);
   }
-
-  // Then let the formatters do their own specific massaging. For each
-  // instance, call the prepareView() method on the formatter object handed by
-  // the entity display.
-  $target_function = function (FieldDefinitionInterface $field_definition, $bundle) use ($displays) {
-    if (isset($displays[$bundle])) {
-      return $displays[$bundle]->getRenderer($field_definition->getName());
-    }
-  };
-  $null = NULL;
-  field_invoke_method_multiple('prepareView', $target_function, $prepare, $null, $null);
 }
 
 /**
- * Returns a renderable array for the fields on an entity.
- *
- * Each field is displayed according to the display options specified in the
- * $display parameter for the given view mode.
- *
- * field_attach_prepare_view() and field_attach_view() are two halves of the
- * same operation. It is safe to call field_attach_prepare_view() multiple times
- * on the same entity before calling field_attach_view() on it, but calling any
- * Field API operation on an entity between passing that entity to these two
- * functions may yield incorrect results.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity with fields to render.
- * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
- *   The entity display object.
- * @param $langcode
- *   The language the field values are to be shown in. If no language is
- *   provided the current language is used.
- * @param array $options
- *   An associative array of additional options. See field_invoke_method() for
- *   details.
- *
- * @return array
- *   A renderable array for the field values.
- *
- * @deprecated as of Drupal 8.0. Use the entity system instead.
+ * @todo Remove when all calls are converted.
  */
 function field_attach_view(EntityInterface $entity, EntityViewDisplayInterface $display, $langcode = NULL, array $options = array()) {
-  // For each field, call the view() method on the formatter object handed
-  // by the entity display.
-  $target_function = function (FieldDefinitionInterface $field_definition) use ($display) {
-    return $display->getRenderer($field_definition->getName());
-  };
-  $null = NULL;
-  $output = field_invoke_method('view', $target_function, $entity, $null, $null, $options);
-
-  // Let other modules alter the renderable array.
-  $view_mode = $display->originalMode;
-  $context = array(
-    'entity' => $entity,
-    'view_mode' => $view_mode,
-    'display_options' => $view_mode,
-    'langcode' => $langcode,
-  );
-  drupal_alter('field_attach_view', $output, $context);
-
-  // Reset the _field_view_prepared flag set in field_attach_prepare_view(),
-  // in case the same entity is displayed with different settings later in
-  // the request.
-  unset($entity->_field_view_prepared);
-
-  return $output;
+  return $display->attachfields($entity, $display);
 }
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index 408d816..c640653 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -344,7 +344,7 @@ function _field_filter_xss_display_allowed_tags() {
 function field_view_value(EntityInterface $entity, $field_name, $item, $display = array(), $langcode = NULL) {
   $output = array();
 
-  if ($field = field_info_field($entity->entityType(), $field_name)) {
+  if ($entity->hasField($field_name)) {
     // Clone the entity since we are going to modify field values.
     $clone = clone $entity;
 
@@ -423,49 +423,33 @@ function field_view_field(ContentEntityInterface $entity, $field_name, $display_
   if (!$entity->hasField($field_name)) {
     return $output;
   }
-  $field_definition = $entity->get($field_name)->getFieldDefinition();
 
-  // Get the formatter object.
+  // Get the display object.
   if (is_string($display_options)) {
     $view_mode = $display_options;
-    $formatter = entity_get_render_display($entity, $view_mode)->getRenderer($field_name);
+    $display = entity_get_render_display($entity, $view_mode);
+    foreach ($entity as $name => $items) {
+      if ($name != $field_name) {
+        $display->removeComponent($name);
+      }
+    }
   }
   else {
     $view_mode = '_custom';
-    // hook_field_attach_display_alter() needs to receive the 'prepared'
-    // $display_options, so we cannot let preparation happen internally.
-    $formatter_manager = Drupal::service('plugin.manager.field.formatter');
-    $display_options = $formatter_manager->prepareConfiguration($field_definition->getType(), $display_options);
-    $formatter = $formatter_manager->getInstance(array(
-      'field_definition' => $field_definition,
-      'view_mode' => $view_mode,
-      'prepare' => FALSE,
-      'configuration' => $display_options,
+    $display = entity_create('entity_display', array(
+      'targetEntityType' => $entity->entityType(),
+      'bundle' => $entity->bundle(),
+      'mode' => $view_mode,
+      'status' => TRUE,
     ));
+    $display->setComponent($field_name, $display_options);
   }
 
-  if ($formatter) {
-    // Apply language fallback.
-    $entity = \Drupal::entityManager()->getTranslationFromContext($entity, $langcode);
-    $items = $entity->get($field_name);
+  $display->prepareFields(array($entity->id() => $entity));
+  $build = $display->attachFields($entity);
 
-    // Run the formatter.
-    $formatter->prepareView(array($entity->id() => $items));
-    $result = $formatter->view($items);
-
-    // Invoke hook_field_attach_view_alter() to let other modules alter the
-    // renderable array, as in a full field_attach_view() execution.
-    $context = array(
-      'entity' => $entity,
-      'view_mode' => $view_mode,
-      'display_options' => $display_options,
-      'langcode' => $entity->language()->id,
-    );
-    drupal_alter('field_attach_view', $result, $context);
-
-    if (isset($result[$field_name])) {
-      $output = $result[$field_name];
-    }
+  if (isset($build[$field_name])) {
+    $output = $build[$field_name];
   }
 
   return $output;
diff --git a/core/modules/field/lib/Drupal/field/Tests/DisplayApiTest.php b/core/modules/field/lib/Drupal/field/Tests/DisplayApiTest.php
index 86e723c..02fff37 100644
--- a/core/modules/field/lib/Drupal/field/Tests/DisplayApiTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/DisplayApiTest.php
@@ -143,7 +143,7 @@ function testFieldViewField() {
     $setting = $display['settings']['test_formatter_setting_multiple'];
     $this->assertNoText($this->label, 'Label was not displayed.');
     $this->assertText('field_test_field_attach_view_alter', 'Alter fired, display passed.');
-    $this->assertText('field language is ' . Language::LANGCODE_NOT_SPECIFIED, 'Language is placed onto the context.');
+    $this->assertText('entity language is ' . Language::LANGCODE_NOT_SPECIFIED, 'Language is placed onto the context.');
     $array = array();
     foreach ($this->values as $delta => $value) {
       $array[] = $delta . ':' . $value['value'];
diff --git a/core/modules/field/tests/modules/field_test/field_test.module b/core/modules/field/tests/modules/field_test/field_test.module
index 8215416..a67aa4c 100644
--- a/core/modules/field/tests/modules/field_test/field_test.module
+++ b/core/modules/field/tests/modules/field_test/field_test.module
@@ -115,12 +115,13 @@ function field_test_field_entity_create(FieldInterface $field) {
  * Implements hook_field_attach_view_alter().
  */
 function field_test_field_attach_view_alter(&$output, $context) {
-  if (!empty($context['display_options']['settings']['alter'])) {
+  $display_options = $context['display']->getComponent('test_field');
+  if (isset($display_options['settings']['alter'])) {
     $output['test_field'][] = array('#markup' => 'field_test_field_attach_view_alter');
   }
 
   if (isset($output['test_field'])) {
-    $output['test_field'][] = array('#markup' => 'field language is ' . $context['langcode']);
+    $output['test_field'][] = array('#markup' => 'entity language is ' . $context['entity']->language()->id);
   }
 }
 
diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php
index d53673c..b576a94 100644
--- a/core/modules/node/node.api.php
+++ b/core/modules/node/node.api.php
@@ -62,9 +62,7 @@
  *   - hook_node_load() (all)
  * - Viewing a single node (calling node_view() - note that the input to
  *   node_view() is a loaded node, so the Loading steps above are already done):
- *   - field_attach_prepare_view()
  *   - hook_entity_prepare_view() (all)
- *   - field_attach_view()
  *   - hook_node_view() (all)
  *   - hook_entity_view() (all)
  *   - hook_node_view_alter() (all)
@@ -72,9 +70,7 @@
  * - Viewing multiple nodes (calling node_view_multiple() - note that the input
  *   to node_view_multiple() is a set of loaded nodes, so the Loading steps
  *   above are already done):
- *   - field_attach_prepare_view()
  *   - hook_entity_prepare_view() (all)
- *   - field_attach_view()
  *   - hook_node_view() (all)
  *   - hook_entity_view() (all)
  *   - hook_node_view_alter() (all)
