diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
index d886224..3af91d3 100644
--- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
+++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
@@ -110,7 +110,7 @@ public function buildContent(array $entities, array $displays, $view_mode, $lang
       $entity->content = array(
         '#view_mode' => $view_mode,
       );
-      $entity->content += field_attach_view($entity, $displays[$entity->bundle()], $langcode);
+      $entity->content += $this->attachFormatters($entity, $displays[$entity->bundle()]);
     }
   }
 
@@ -265,4 +265,108 @@ public function resetCache(array $entities = NULL) {
       \Drupal::cache($this->cacheBin)->deleteTags(array($this->entityType . '_view' => TRUE));
     }
   }
+
+  /**
+   * Prepares field data prior to display.
+   *
+   * This method lets 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.
+   *
+   * prepareFormatters() and attachFormatters() are two halves of the same
+   * operation. It is safe to call prepareFormatters() multiple times
+   * on the same entity before calling attachFormatters() 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[] $entities
+   *   An array of entities, keyed by entity ID.
+   * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface[] $displays
+   *   An array of entity display objects, keyed by bundle name.
+   */
+  public function prepareFormatters(array $entities, array $displays) {
+    // 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();
+    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;
+      }
+    }
+
+    // Group entities by bundles.
+    $entities_by_bundle = array();
+    foreach ($entities as $id => $entity) {
+      $entities_by_bundle[$entity->bundle()][$id] = $entity;
+    }
+
+    foreach ($entities_by_bundle as $bundle => $bundle_entities) {
+      $display = $displays[$bundle];
+
+      // For each field in the bundle, group items across all entities and
+      // pass them to the formatter's prepareView() method.
+      $field_definitions = current($bundle_entities)->getPropertyDefinitions();
+      foreach ($field_definitions as $field_name => $definition) {
+        if ($formatter = $display->getRenderer($field_name)) {
+          $entities_items = array();
+          foreach ($bundle_entities as $id => $entity) {
+            $entities_items[$id] = $entity->get($field_name);
+          }
+          $formatter->prepareView($entities_items);
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns a renderable array for the fields on an entity.
+   *
+   * Each field is displayed according to the display options specified in the
+   * $display.
+   *
+   * prepareFormatters() and attachFormatters() are two halves of the same
+   * operation. It is safe to call prepareFormatters() multiple times
+   * on the same entity before calling attachFormatters() 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.
+   *
+   * @return array
+   *   A renderable array for the field values.
+   */
+  public function attachFormatters(EntityInterface $entity, EntityViewDisplayInterface $display) {
+    $output = array();
+
+    foreach ($entity as $field_name => $items) {
+      if ($formatter = $display->getRenderer($field_name)) {
+        $output += $formatter->view($items);
+      }
+    }
+
+    // Let other modules alter the renderable array.
+    $view_mode = $display->originalMode;
+    $context = array(
+      'entity' => $entity,
+      'view_mode' => $view_mode,
+      // @todo ??? see field_view_field()...
+      'display_options' => $view_mode,
+    );
+    \Drupal::moduleHandler()->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;
+  }
 }
diff --git a/core/lib/Drupal/Core/Field/Annotation/FieldFormatter.php b/core/lib/Drupal/Core/Field/Annotation/FieldFormatter.php
index 1ef899e..6b5bde3 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. Formatter methods are
+ * typically called by the EntityViewBuilderInterface::attachFormatters() and @todo methods.
  *
  * Additional annotation keys for formatters can be defined in
  * hook_field_formatter_info_alter().
diff --git a/core/modules/field/field.api.php b/core/modules/field/field.api.php
index 3f59be4..7b319d9 100644
--- a/core/modules/field/field.api.php
+++ b/core/modules/field/field.api.php
@@ -375,7 +375,6 @@ function hook_field_attach_extract_form_values(\Drupal\Core\Entity\EntityInterfa
  *     $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.
  */
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..b945097 100644
--- a/core/modules/field/field.deprecated.inc
+++ b/core/modules/field/field.deprecated.inc
@@ -415,30 +415,7 @@ function field_attach_extract_form_values(EntityInterface $entity, $form, &$form
  * @deprecated as of Drupal 8.0. Use the entity system instead.
  */
 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();
-  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;
-    }
-  }
-
-  // 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);
+  \Drupal::entityManager()->getViewBuilder($entity_type)->prepareFormatters($entities, $displays);
 }
 
 /**
@@ -470,28 +447,5 @@ function field_attach_prepare_view($entity_type, array $entities, array $display
  * @deprecated as of Drupal 8.0. Use the entity system instead.
  */
 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 \Drupal::entityManager()->getViewBuilder($entity->entityType())->attachFormatters($entity, $display);
 }
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..3648112 100644
--- a/core/modules/field/tests/modules/field_test/field_test.module
+++ b/core/modules/field/tests/modules/field_test/field_test.module
@@ -120,7 +120,7 @@ function field_test_field_attach_view_alter(&$output, $context) {
   }
 
   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)
