diff --git a/core/modules/field/field.api.php b/core/modules/field/field.api.php
index 166c85d..fb8151d 100644
--- a/core/modules/field/field.api.php
+++ b/core/modules/field/field.api.php
@@ -837,8 +837,14 @@ function hook_field_widget_properties_alter(&$widget_properties, $context) {
  * which the field is attached is displayed. Fields of a given
  * @link field_types field type @endlink may be displayed using more than one
  * formatter. In this case, the Field UI module allows the site builder to
- * choose which formatter to use. Field formatters are defined by implementing
- * hook_field_formatter_info().
+ * choose which formatter to use.
+ *
+ * Formatters are Plugins managed by the
+ * Drupal\field\Plugin\Type\Formatter\FormatterPluginManager class. A formatter
+ * is implemented by providing a class that implements
+ * Drupal\field\Plugin\Type\Formatter\FormatterInterface (in most cases, by
+ * subclassing Drupal\field\Plugin\Type\Formatter\FormatterBase), and provides
+ * the proper annotation block.
  *
  * @see field
  * @see field_types
@@ -846,62 +852,6 @@ function hook_field_widget_properties_alter(&$widget_properties, $context) {
  */
 
 /**
- * Expose Field API formatter types.
- *
- * 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.
- *
- * @return
- *   An array describing the formatter types implemented by the module.
- *   The keys are formatter type names. To avoid name clashes, formatter type
- *   names should be prefixed with the name of the module that exposes them.
- *   The values are arrays describing the formatter type, with the following
- *   key/value pairs:
- *   - label: The human-readable name of the formatter type.
- *   - description: A short description for the formatter type.
- *   - field types: An array of field types the formatter supports.
- *   - settings: An array whose keys are the names of the settings available
- *     for the formatter type, and whose values are the default values for
- *     those settings.
- *
- * @see hook_field_formatter_info_alter()
- * @see hook_field_formatter_view()
- * @see hook_field_formatter_prepare_view()
- */
-function hook_field_formatter_info() {
-  return array(
-    'text_default' => array(
-      'label' => t('Default'),
-      'field types' => array('text', 'text_long', 'text_with_summary'),
-    ),
-    'text_plain' => array(
-      'label' => t('Plain text'),
-      'field types' => array('text', 'text_long', 'text_with_summary'),
-    ),
-
-    // The text_trimmed formatter displays the trimmed version of the
-    // full element of the field. It is intended to be used with text
-    // and text_long fields. It also works with text_with_summary
-    // fields though the text_summary_or_trimmed formatter makes more
-    // sense for that field type.
-    'text_trimmed' => array(
-      'label' => t('Trimmed'),
-      'field types' => array('text', 'text_long', 'text_with_summary'),
-    ),
-
-    // The 'summary or trimmed' field formatter for text_with_summary
-    // fields displays returns the summary element of the field or, if
-    // the summary is empty, the trimmed version of the full element
-    // of the field.
-    'text_summary_or_trimmed' => array(
-      'label' => t('Summary or trimmed'),
-      'field types' => array('text_with_summary'),
-    ),
-  );
-}
-
-/**
  * Perform alterations on Field API formatter types.
  *
  * @param $info
@@ -919,150 +869,6 @@ function hook_field_formatter_info_alter(&$info) {
 }
 
 /**
- * Allow formatters to load information for field values being displayed.
- *
- * This should be used when a formatter needs to load additional information
- * from the database in order to render a field, for example a reference field
- * which displays properties of the referenced entities such as name or type.
- *
- * This hook is called after the field type's own hook_field_prepare_view().
- *
- * Unlike most other field hooks, this hook operates on multiple entities. The
- * $entities, $instances and $items parameters are arrays keyed by entity ID.
- * For performance reasons, information for all available entities should be
- * loaded in a single query where possible.
- *
- * @param $entity_type
- *   The type of $entity.
- * @param $entities
- *   Array of entities being displayed, keyed by entity ID.
- * @param $field
- *   The field structure for the operation.
- * @param $instances
- *   Array of instance structures for $field for each entity, keyed by entity
- *   ID.
- * @param $langcode
- *   The language the field values are to be shown in. If no language is
- *   provided the current language is used.
- * @param $items
- *   Array of field values for the entities, keyed by entity ID.
- * @param $displays
- *   Array of display settings to use for each entity, keyed by entity ID.
- *
- * @return
- *   Changes or additions to field values are done by altering the $items
- *   parameter by reference.
- */
-function hook_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
-  $tids = array();
-
-  // Collect every possible term attached to any of the fieldable entities.
-  foreach ($entities as $id => $entity) {
-    foreach ($items[$id] as $delta => $item) {
-      // Force the array key to prevent duplicates.
-      $tids[$item['tid']] = $item['tid'];
-    }
-  }
-
-  if ($tids) {
-    $terms = taxonomy_term_load_multiple($tids);
-
-    // Iterate through the fieldable entities again to attach the loaded term
-    // data.
-    foreach ($entities as $id => $entity) {
-      $rekey = FALSE;
-
-      foreach ($items[$id] as $delta => $item) {
-        // Check whether the taxonomy term field instance value could be loaded.
-        if (isset($terms[$item['tid']])) {
-          // Replace the instance value with the term data.
-          $items[$id][$delta]['taxonomy_term'] = $terms[$item['tid']];
-        }
-        // Otherwise, unset the instance value, since the term does not exist.
-        else {
-          unset($items[$id][$delta]);
-          $rekey = TRUE;
-        }
-      }
-
-      if ($rekey) {
-        // Rekey the items array.
-        $items[$id] = array_values($items[$id]);
-      }
-    }
-  }
-}
-
-/**
- * Build a renderable array for a field value.
- *
- * @param $entity_type
- *   The type of $entity.
- * @param $entity
- *   The entity being displayed.
- * @param $field
- *   The field structure.
- * @param $instance
- *   The field instance.
- * @param $langcode
- *   The language associated with $items.
- * @param $items
- *   Array of values for this field.
- * @param $display
- *   The display settings to use, as found in the 'display' entry of instance
- *   definitions. The array notably contains the following keys and values;
- *   - type: The name of the formatter to use.
- *   - settings: The array of formatter settings.
- *
- * @return
- *   A renderable array for the $items, as an array of child elements keyed
- *   by numeric indexes starting from 0.
- */
-function hook_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
-  $element = array();
-  $settings = $display['settings'];
-
-  switch ($display['type']) {
-    case 'sample_field_formatter_simple':
-      // Common case: each value is displayed individually in a sub-element
-      // keyed by delta. The field.tpl.php template specifies the markup
-      // wrapping each value.
-      foreach ($items as $delta => $item) {
-        $element[$delta] = array('#markup' => $settings['some_setting'] . $item['value']);
-      }
-      break;
-
-    case 'sample_field_formatter_themeable':
-      // More elaborate formatters can defer to a theme function for easier
-      // customization.
-      foreach ($items as $delta => $item) {
-        $element[$delta] = array(
-          '#theme' => 'mymodule_theme_sample_field_formatter_themeable',
-          '#data' => $item['value'],
-          '#some_setting' => $settings['some_setting'],
-        );
-      }
-      break;
-
-    case 'sample_field_formatter_combined':
-      // Some formatters might need to display all values within a single piece
-      // of markup.
-      $rows = array();
-      foreach ($items as $delta => $item) {
-        $rows[] = array($delta, $item['value']);
-      }
-      $element[0] = array(
-        '#theme' => 'table',
-        '#header' => array(t('Delta'), t('Value')),
-        '#rows' => $rows,
-      );
-      break;
-  }
-
-  return $element;
-}
-
-/**
  * @} End of "defgroup field_formatter".
  */
 
@@ -2113,27 +1919,27 @@ function hook_field_info_max_weight($entity_type, $bundle, $context) {
  * hook involves reading from the database, it is highly recommended to
  * statically cache the information.
  *
- * @param $display
+ * @param $display_properties
  *   The display settings that will be used to display the field values, as
  *   found in the 'display' key of $instance definitions.
  * @param $context
  *   An associative array containing:
  *   - entity_type: The entity type; e.g., 'node' or 'user'.
+ *   - bundle: The bundle: e.g., 'page' or 'article'.
  *   - field: The field being rendered.
  *   - instance: The instance being rendered.
- *   - entity: The entity being rendered.
  *   - view_mode: The view mode, e.g. 'full', 'teaser'...
  *
  * @see hook_field_display_ENTITY_TYPE_alter()
  */
-function hook_field_display_alter(&$display, $context) {
+function hook_field_display_alter(&$display_properties, $context) {
   // Leave field labels out of the search index.
   // Note: The check against $context['entity_type'] == 'node' could be avoided
   // by using hook_field_display_node_alter() instead of
   // hook_field_display_alter(), resulting in less function calls when
   // rendering non-node entities.
   if ($context['entity_type'] == 'node' && $context['view_mode'] == 'search_index') {
-    $display['label'] = 'hidden';
+    $display_properties['label'] = 'hidden';
   }
 }
 
@@ -2148,23 +1954,23 @@ function hook_field_display_alter(&$display, $context) {
  * hook involves reading from the database, it is highly recommended to
  * statically cache the information.
  *
- * @param $display
+ * @param $display_properties
  *   The display settings that will be used to display the field values, as
  *   found in the 'display' key of $instance definitions.
  * @param $context
  *   An associative array containing:
  *   - entity_type: The entity type; e.g., 'node' or 'user'.
+ *   - bundle: The bundle: e.g., 'page' or 'article'.
  *   - field: The field being rendered.
  *   - instance: The instance being rendered.
- *   - entity: The entity being rendered.
  *   - view_mode: The view mode, e.g. 'full', 'teaser'...
  *
  * @see hook_field_display_alter()
  */
-function hook_field_display_ENTITY_TYPE_alter(&$display, $context) {
+function hook_field_display_ENTITY_TYPE_alter(&$display_properties, $context) {
   // Leave field labels out of the search index.
   if ($context['view_mode'] == 'search_index') {
-    $display['label'] = 'hidden';
+    $display_properties['label'] = 'hidden';
   }
 }
 
diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc
index cef1d5d..dd99904 100644
--- a/core/modules/field/field.attach.inc
+++ b/core/modules/field/field.attach.inc
@@ -197,6 +197,145 @@ function field_invoke_method($method, Closure $target_closure, EntityInterface $
 }
 
 /**
+ * Invoke a method across fields on multiple entities.
+ *
+ * @param $method
+ *   The name of the method to invoke.
+ * @param $target
+ *   A closure that receives an $instance object and returns the object on
+ *   which the method should be invoked.
+ * @param $entities
+ *   An array of entities, keyed by entity id.
+ * @param $a
+ *   A parameter for the invoked method.
+ * @param $b
+ *   A parameter for the invoked method.
+ * @param $options
+ *   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. NOTE: This option is not compatible with the 'deleted' option; the
+ *    'field_id' option should be used instead.
+ *  - 'field_id': The id of the field whose operation should be invoked. By
+ *    default, the operation is invoked on all the fields in the entity's'
+ *    bundles.
+ *  - 'deleted': If TRUE, the function will operate on deleted fields as well as
+ *    non-deleted fields. If unset or FALSE, only non-deleted fields are
+ *    operated on.
+ *  - 'langcode': A language code or an array of language codes keyed by field
+ *    name. It will be used to narrow down to a single value the available
+ *    languages to act on.
+ *
+ * @return
+ *   An array of returned values keyed by entity id.
+ *
+ * @see field_invoke_method()
+ */
+function field_invoke_method_multiple($method, Closure $target, $entities, &$a = NULL, &$b = NULL, $options = array()) {
+  // Merge default options.
+  $default_options = array(
+    'deleted' => FALSE,
+    'langcode' => NULL,
+  );
+  $options += $default_options;
+
+  $fields = array();
+  $grouped_instances = array();
+  $grouped_entities = array();
+  $grouped_items = array();
+  $grouped_objects = array();
+  $return = array();
+
+  // Go through the entities and collect the fields on which the method should
+  // be called.
+  //
+  // We group fields by id, not by name, because this function can operate on
+  // deleted fields which may have non-unique names. However, entities can only
+  // contain data for a single field for each name, even if that field
+  // is deleted, so we reference field data via the $entity->$field_name
+  // property.
+  foreach ($entities as $entity) {
+    $id = $entity->id();
+    $entity_type = $entity->entityType();
+
+    // Determine the list of instances to iterate on.
+    $instances = _field_invoke_get_instances($entity_type, $entity->bundle(), $options);
+
+    foreach ($instances as $instance) {
+      $field_id = $instance['field_id'];
+      $field = field_info_field_by_id($field_id);
+      $field_name = $field['field_name'];
+
+      // Let the closure determine the target object on which the method should
+      // be called.
+      if (empty($grouped_objects[$field_id])) {
+        $grouped_objects[$field_id] = $target($instance);
+      }
+
+      if (method_exists($grouped_objects[$field_id], $method)) {
+        // Add the field to the list of fields to invoke the hook on.
+        if (!isset($fields[$field_id])) {
+          $fields[$field_id] = $field;
+        }
+
+        // Unless a language code suggestion is provided we iterate on all the
+        // available language codes.
+        $available_langcodes = field_available_languages($entity_type, $field);
+        $langcode = !empty($options['langcode'][$id]) ? $options['langcode'][$id] : $options['langcode'];
+        $langcodes = _field_language_suggestion($available_langcodes, $langcode, $field_name);
+        foreach ($langcodes as $langcode) {
+          $grouped_items[$field_id][$langcode][$id] = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array();
+          // Group the instances and entities corresponding to the current
+          // field.
+          $grouped_instances[$field_id][$langcode][$id] = $instance;
+          $grouped_entities[$field_id][$langcode][$id] = $entities[$id];
+        }
+      }
+    }
+    // Initialize the return value for each entity.
+    $return[$id] = array();
+  }
+
+  // For each field, invoke the method and collect results.
+  foreach ($fields as $field_id => $field) {
+    $field_name = $field['field_name'];
+
+    // Iterate over all the field translations.
+    foreach ($grouped_items[$field_id] as $langcode => &$items) {
+      $entities = $grouped_entities[$field_id][$langcode];
+      $instances = $grouped_instances[$field_id][$langcode];
+      $result = $grouped_objects[$field_id]->$method($entities, $instances, $items, $langcode, $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;
+          }
+        }
+      }
+    }
+
+    // Populate field values back in the entities, but avoid replacing missing
+    // fields with an empty array (those are not equivalent on update).
+    foreach ($grouped_entities[$field_id] as $langcode => $entities) {
+      foreach ($entities as $id => $entity) {
+        if ($grouped_items[$field_id][$langcode][$id] !== array() || isset($entity->{$field_name}[$langcode])) {
+          $entity->{$field_name}[$langcode] = $grouped_items[$field_id][$langcode][$id];
+        }
+      }
+    }
+  }
+
+  return $return;
+}
+
+/**
  * Invoke a field hook.
  *
  * @param $op
@@ -535,6 +674,23 @@ function _field_invoke_widget_target() {
 }
 
 /**
+ * Defines a 'target closure' for field_invoke_method().
+ *
+ * Used to invoke methods on an instance's formatter.
+ *
+ * @param mixed $display
+ *   @todo
+ *
+ * @return Closure
+ *   A 'target closure' for field_invoke_method().
+ */
+function _field_invoke_formatter_target($display) {
+  return function ($instance) use ($display) {
+    return $instance->getFormatter($display);
+  };
+}
+
+/**
  * Add form elements for all fields for an entity to a form structure.
  *
  * The form elements for the entity's fields are added by reference as direct
@@ -1187,7 +1343,8 @@ function field_attach_prepare_view($entity_type, $entities, $view_mode, $langcod
   // Then let the formatters do their own specific massaging.
   // field_default_prepare_view() takes care of dispatching to the correct
   // formatters according to the display settings for the view mode.
-  _field_invoke_multiple_default('prepare_view', $entity_type, $prepare, $view_mode, $null, $options);
+  // @todo not sure about that...
+  field_invoke_method_multiple('prepareView', _field_invoke_formatter_target($view_mode), $prepare, $view_mode, $null, $options);
 }
 
 /**
@@ -1244,7 +1401,7 @@ function field_attach_view($entity_type, EntityInterface $entity, $view_mode, $l
 
   // Invoke field_default_view().
   $null = NULL;
-  $output = _field_invoke_default('view', $entity_type, $entity, $view_mode, $null, $options);
+  $output = field_invoke_method('view', _field_invoke_formatter_target($view_mode), $entity, $view_mode, $null, $options);
 
   // Add custom weight handling.
   $output['#pre_render'][] = '_field_extra_fields_pre_render';
diff --git a/core/modules/field/field.default.inc b/core/modules/field/field.default.inc
index 7d1a309..afaef93 100644
--- a/core/modules/field/field.default.inc
+++ b/core/modules/field/field.default.inc
@@ -74,127 +74,6 @@ function field_default_insert($entity_type, $entity, $field, $instance, $langcod
 }
 
 /**
- * Invokes hook_field_formatter_prepare_view() on the relevant formatters.
- *
- * @param $entity_type
- *   The type of $entity; e.g. 'node' or 'user'.
- * @param $entities
- *   An array of entities being displayed, keyed by entity id.
- * @param $field
- *   The field structure for the operation.
- * @param $instances
- *   Array of instance structures for $field for each entity, keyed by entity
- *   id.
- * @param $langcode
- *   The language associated to $items.
- * @param $items
- *   Array of field values already loaded for the entities, keyed by entity id.
- * @param $display
- *   Can be either:
- *   - the name of a view mode
- *   - or an array of display settings to use for display, as found in the
- *     'display' entry of $instance definitions.
- */
-function field_default_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $display) {
-  // Group entities, instances and items by formatter module.
-  $modules = array();
-  foreach ($instances as $id => $instance) {
-    if (is_string($display)) {
-      $view_mode = $display;
-      $instance_display = field_get_display($instance, $view_mode, $entities[$id]);
-    }
-    else {
-      $instance_display = $display;
-    }
-
-    if ($instance_display['type'] !== 'hidden') {
-      $module = $instance_display['module'];
-      $modules[$module] = $module;
-      $grouped_entities[$module][$id] = $entities[$id];
-      $grouped_instances[$module][$id] = $instance;
-      $grouped_displays[$module][$id] = $instance_display;
-      // hook_field_formatter_prepare_view() alters $items by reference.
-      $grouped_items[$module][$id] = &$items[$id];
-    }
-  }
-
-  foreach ($modules as $module) {
-    // Invoke hook_field_formatter_prepare_view().
-    $function = $module . '_field_formatter_prepare_view';
-    if (function_exists($function)) {
-      $function($entity_type, $grouped_entities[$module], $field, $grouped_instances[$module], $langcode, $grouped_items[$module], $grouped_displays[$module]);
-    }
-  }
-}
-
-/**
- * Builds a renderable array for one field on one entity instance.
- *
- * @param $entity_type
- *   The type of $entity; e.g. 'node' or 'user'.
- * @param $entity
- *   A single object of type $entity_type.
- * @param $field
- *   The field structure for the operation.
- * @param $instance
- *   An array containing each field on $entity's bundle.
- * @param $langcode
- *   The language associated to $items.
- * @param $items
- *   Array of field values already loaded for the entities, keyed by entity id.
- * @param $display
- *   Can be either:
- *   - the name of a view mode;
- *   - or an array of custom display settings, as found in the 'display' entry
- *     of $instance definitions.
- */
-function field_default_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
-  $addition = array();
-
-  // Prepare incoming display specifications.
-  if (is_string($display)) {
-    $view_mode = $display;
-    $display = field_get_display($instance, $view_mode, $entity);
-  }
-  else {
-    $view_mode = '_custom_display';
-  }
-
-  if ($display['type'] !== 'hidden') {
-    // Calling the formatter function through module_invoke() can have a
-    // performance impact on pages with many fields and values.
-    $function = $display['module'] . '_field_formatter_view';
-    if (function_exists($function)) {
-      $elements = $function($entity_type, $entity, $field, $instance, $langcode, $items, $display);
-
-      if ($elements) {
-        $info = array(
-          '#theme' => 'field',
-          '#weight' => $display['weight'],
-          '#title' => $instance['label'],
-          '#access' => field_access('view', $field, $entity_type, $entity),
-          '#label_display' => $display['label'],
-          '#view_mode' => $view_mode,
-          '#language' => $langcode,
-          '#field_name' => $field['field_name'],
-          '#field_type' => $field['type'],
-          '#field_translatable' => $field['translatable'],
-          '#entity_type' => $entity_type,
-          '#bundle' => $entity->bundle(),
-          '#object' => $entity,
-          '#items' => $items,
-          '#formatter' => $display['type']
-        );
-
-        $addition[$field['field_name']] = array_merge($info, $elements);
-      }
-    }
-  }
-
-  return $addition;
-}
-
-/**
  * Copies source field values into the entity to be prepared.
  *
  * @param $entity_type
diff --git a/core/modules/field/field.info.inc b/core/modules/field/field.info.inc
index 41dd9a6..ea863c9 100644
--- a/core/modules/field/field.info.inc
+++ b/core/modules/field/field.info.inc
@@ -49,22 +49,10 @@ function field_info_cache_clear() {
  *     instance_settings, default_widget, default_formatter, and behaviors
  *     from hook_field_info(), as well as module, giving the module that exposes
  *     the field type.
- *   - 'widget types': Array of hook_field_widget_info() results, keyed by
- *     widget_type. Each element has the following components: label, field
- *     types, settings, weight, and behaviors from hook_field_widget_info(),
- *     as well as module, giving the module that exposes the widget type.
- *   - 'formatter types': Array of hook_field_formatter_info() results, keyed by
- *     formatter_type. Each element has the following components: label, field
- *     types, and behaviors from hook_field_formatter_info(), as well as
- *     module, giving the module that exposes the formatter type.
  *   - 'storage types': Array of hook_field_storage_info() results, keyed by
  *     storage type names. Each element has the following components: label,
  *     description, and settings from hook_field_storage_info(), as well as
  *     module, giving the module that exposes the storage type.
- *   - 'fieldable types': Array of hook_entity_info() results, keyed by
- *     entity_type. Each element has the following components: name, id key,
- *     revision key, bundle key, cacheable, and bundles from hook_entity_info(),
- *     as well as module, giving the module that exposes the entity type.
  *
  * @see _field_info_collate_types_reset()
  */
@@ -90,7 +78,6 @@ function _field_info_collate_types() {
     else {
       $info = array(
         'field types' => array(),
-        'formatter types' => array(),
         'storage types' => array(),
       );
 
@@ -109,20 +96,6 @@ function _field_info_collate_types() {
       }
       drupal_alter('field_info', $info['field types']);
 
-      // Populate formatter types.
-      foreach (module_implements('field_formatter_info') as $module) {
-        $formatter_types = (array) module_invoke($module, 'field_formatter_info');
-        foreach ($formatter_types as $name => $formatter_info) {
-          // Provide defaults.
-          $formatter_info += array(
-            'settings' => array(),
-          );
-          $info['formatter types'][$name] = $formatter_info;
-          $info['formatter types'][$name]['module'] = $module;
-        }
-      }
-      drupal_alter('field_formatter_info', $info['formatter types']);
-
       // Populate storage types.
       foreach (module_implements('field_storage_info') as $module) {
         $storage_types = (array) module_invoke($module, 'field_storage_info');
@@ -296,66 +269,10 @@ function _field_info_prepare_instance($instance, $field) {
     $instance['default_value'] = NULL;
   }
 
-  foreach ($instance['display'] as $view_mode => $display) {
-    $instance['display'][$view_mode] = _field_info_prepare_instance_display($field, $display);
-  }
-
-  // Fallback to 'hidden' for view modes configured to use custom display
-  // settings, and for which the instance has no explicit settings.
-  $entity_info = entity_get_info($instance['entity_type']);
-  $view_modes = array_merge(array('default'), array_keys($entity_info['view modes']));
-  $view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']);
-  foreach ($view_modes as $view_mode) {
-    if ($view_mode == 'default' || !empty($view_mode_settings[$view_mode]['custom_settings'])) {
-      if (!isset($instance['display'][$view_mode])) {
-        $instance['display'][$view_mode] = array(
-          'type' => 'hidden',
-          'label' => 'above',
-          'settings' => array(),
-          'weight' => 0,
-        );
-      }
-    }
-  }
-
   return $instance;
 }
 
 /**
- * Adapts display specifications to the current run-time context.
- *
- * @param $field
- *   The field structure for the instance.
- * @param $display
- *   Display specifications as found in
- *   $instance['display']['some_view_mode'].
- */
-function _field_info_prepare_instance_display($field, $display) {
-  $field_type = field_info_field_types($field['type']);
-
-  // Fill in default values.
-  $display += array(
-    'label' => 'above',
-    'type' => $field_type['default_formatter'],
-    'settings' => array(),
-    'weight' => 0,
-  );
-  if ($display['type'] != 'hidden') {
-    $formatter_type = field_info_formatter_types($display['type']);
-    // Fallback to default formatter if formatter type is not available.
-    if (!$formatter_type) {
-      $display['type'] = $field_type['default_formatter'];
-      $formatter_type = field_info_formatter_types($display['type']);
-    }
-    $display['module'] = $formatter_type['module'];
-    // Fill in default settings for the formatter.
-    $display['settings'] += field_info_formatter_settings($display['type']);
-  }
-
-  return $display;
-}
-
-/**
  * Prepares 'extra fields' for the current run-time context.
  *
  * @param $extra_fields
@@ -481,15 +398,11 @@ function field_info_widget_types($widget_type = NULL) {
  *   keyed by formatter type name.
  */
 function field_info_formatter_types($formatter_type = NULL) {
-  $info = _field_info_collate_types();
-  $formatter_types = $info['formatter types'];
   if ($formatter_type) {
-    if (isset($formatter_types[$formatter_type])) {
-      return $formatter_types[$formatter_type];
-    }
+    return field_get_plugin_manager('formatter')->getDefinition($formatter_type);
   }
   else {
-    return $formatter_types;
+    return field_get_plugin_manager('formatter')->getDefinitions();
   }
 }
 
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index ce652cf..fad596b 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -507,6 +507,7 @@ function field_get_plugin_manager($plugin_type) {
 
   $classes = array(
     'widget' => 'Drupal\field\Plugin\Type\Widget\WidgetPluginManager',
+    'formatter' => 'Drupal\field\Plugin\Type\Formatter\FormatterPluginManager',
   );
 
   if (isset($classes[$plugin_type])) {
@@ -924,45 +925,48 @@ function field_view_value($entity_type, $entity, $field_name, $item, $display =
 function field_view_field($entity_type, $entity, $field_name, $display = array(), $langcode = NULL) {
   $output = array();
 
-  if ($field = field_info_field($field_name)) {
-    if (is_array($display)) {
-      // When using custom display settings, fill in default values.
-      $display = _field_info_prepare_instance_display($field, $display);
-    }
-
-    // Hook invocations are done through the _field_invoke() functions in
-    // 'single field' mode, to reuse the language fallback logic.
-    // Determine the actual language code to display for the field, given the
-    // language codes available in the field data.
-    $display_langcode = field_language($entity_type, $entity, $field_name, $langcode);
-    $options = array('field_name' => $field_name, 'langcode' => $display_langcode);
-    $null = NULL;
-
-    // Invoke prepare_view steps if needed.
-    if (empty($entity->_field_view_prepared)) {
-      $id = $entity->id();
-
-      // First let the field types do their preparation.
-      _field_invoke_multiple('prepare_view', $entity_type, array($id => $entity), $display, $null, $options);
-      // Then let the formatters do their own specific massaging.
-      _field_invoke_multiple_default('prepare_view', $entity_type, array($id => $entity), $display, $null, $options);
+  if ($instance = field_info_instance($entity_type, $field_name, $entity->bundle())) {
+    if (is_array($display) && empty($display['type'])) {
+      $field = field_info_field($instance['field_name']);
+      $field_type_info = field_info_field_types($field['type']);
+      $display['type'] = $field_type_info['default_formatter'];
     }
+    if ($formatter = $instance->getFormatter($display)) {
+      $display_langcode = field_language($entity_type, $entity, $field_name, $langcode);
+      $items = $entity->{$field_name}[$display_langcode];
+
+      // Invoke prepare_view steps if needed.
+      if (empty($entity->_field_view_prepared)) {
+        $id = $entity->id();
+
+        // First let the field types do their preparation.
+        // @todo invoke hook_field_prepare_view() directly ?
+        $options = array('field_name' => $field_name, 'langcode' => $display_langcode);
+        $null = NULL;
+        _field_invoke_multiple('prepare_view', $entity_type, array($id => $entity), $display, $null, $options);
+
+        // Then let the formatters do their own specific massaging.
+        $items_multi = array($id => $entity->{$field_name}[$display_langcode]);
+        $formatter->prepareView(array($id => $entity), $display_langcode, $items_multi);
+        $items = $items_multi[$id];
+      }
 
-    // Build the renderable array.
-    $result = _field_invoke_default('view', $entity_type, $entity, $display, $null, $options);
-
-    // 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_type' => $entity_type,
-      'entity' => $entity,
-      'view_mode' => '_custom',
-      'display' => $display,
-    );
-    drupal_alter('field_attach_view', $result, $context);
-
-    if (isset($result[$field_name])) {
-      $output = $result[$field_name];
+      // Build the renderable array.
+      $result = $formatter->view($entity, $display_langcode, $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_type' => $entity_type,
+        'entity' => $entity,
+        'view_mode' => '_custom',
+        'display' => $display,
+      );
+      drupal_alter('field_attach_view', $result, $context);
+
+      if (isset($result[$field_name])) {
+        $output = $result[$field_name];
+      }
     }
   }
 
diff --git a/core/modules/field/lib/Drupal/field/FieldInstance.php b/core/modules/field/lib/Drupal/field/FieldInstance.php
index 2ac9077..3fc2b14 100644
--- a/core/modules/field/lib/Drupal/field/FieldInstance.php
+++ b/core/modules/field/lib/Drupal/field/FieldInstance.php
@@ -19,6 +19,11 @@ class FieldInstance implements \ArrayAccess {
    */
   protected $widget;
 
+  /**
+   * @var array
+   */
+  protected $formatters;
+
   public function __construct($definition) {
     $this->definition = $definition;
   }
@@ -55,6 +60,89 @@ class FieldInstance implements \ArrayAccess {
   }
 
   /**
+   * Returns the Widget plugin for the instance.
+   *
+   * @param mixed $display_properties
+   *   Can be either:
+   *   - The name of a view mode.
+   *     @todo : from_field_view_field() : "If no display settings are found for
+   *     the view mode, the settings for the 'default' view mode will be used." ??
+   *   - An array of display properties, as accepted by setDisplayConfig().
+   *
+   * @return Drupal\field\Plugin\Type\Formatter\FormatterInterface
+   *   The Formatter plugin to be used for the instance, or NULL if the field
+   *   is hidden.
+   */
+  public function getFormatter($display_properties) {
+    if (is_string($display_properties)) {
+      // A view mode was provided. Switch to 'default' if the view mode is not
+      // configured to use dedicated settings.
+      $view_mode = $display_properties;
+      $view_mode_settings = field_view_mode_settings($this->definition['entity_type'], $this->definition['bundle']);
+      $actual_mode = (!empty($view_mode_settings[$view_mode]['custom_settings']) ? $view_mode : 'default');
+
+      if (isset($this->formatters[$actual_mode])) {
+        return $this->formatters[$actual_mode];
+      }
+
+      // Switch to 'hidden' if the instance has no properties for the view
+      // mode.
+      if (isset($this->definition['display'][$actual_mode])) {
+        $display_properties = $this->definition['display'][$actual_mode];
+      }
+      else {
+        $display_properties = array(
+          'type' => 'hidden',
+          'settings' => array(),
+          'label' => 'above',
+          'weight' => 0,
+        );
+      }
+
+      // Let modules alter the widget properties.
+      $context = array(
+        'entity_type' => $this->definition['entity_type'],
+        'bundle' => $this->definition['bundle'],
+        'field' => field_info_field($this->definition['field_name']),
+        'instance' => $this,
+        'view_mode' => $view_mode,
+      );
+      drupal_alter(array('field_display', 'field_display_' . $this->definition['entity_type']), $display_properties, $context);
+    }
+    else {
+      // Arbitrary display settings. Make sure defaults are present.
+      $display_properties += array(
+        'settings' => array(),
+        'label' => 'above',
+        'weight' => 0,
+      );
+      $view_mode = '_custom_display';
+    }
+
+    if (!empty($display_properties['type']) && $display_properties['type'] != 'hidden') {
+      $options = array(
+        'instance' => $this,
+        'type' => $display_properties['type'],
+        'settings' => $display_properties['settings'],
+        'label' => $display_properties['label'],
+        'weight' => $display_properties['weight'],
+        'view_mode' => $view_mode,
+      );
+      $formatter = field_get_plugin_manager('formatter')->getInstance($options);
+    }
+    else {
+      $formatter = NULL;
+    }
+
+    // Persist the object if we were not passed custom display settings.
+    if (isset($actual_mode)) {
+      $this->formatters[$actual_mode] = $formatter;
+    }
+
+    return $formatter;
+  }
+
+  /**
    * Implements ArrayAccess::offsetExists().
    */
   public function offsetExists($offset) {
@@ -78,11 +166,14 @@ class FieldInstance implements \ArrayAccess {
     }
     $this->definition[$offset] = $value;
 
-    // If the widget properties changed, the widget plugin needs to be
-    // re-instanciated.
+    // If the widget or formatter properties changed, the corrsponding plugins
+    // need to be re-instanciated.
     if ($offset == 'widget') {
       unset($this->widget);
     }
+    if ($offset == 'display') {
+      unset($this->formatters);
+    }
   }
 
   /**
@@ -91,11 +182,14 @@ class FieldInstance implements \ArrayAccess {
   public function offsetUnset($offset) {
     unset($this->definition[$offset]);
 
-    // If the widget properties changed, the widget plugin needs to be
-    // re-instanciated.
+    // If the widget or formatter properties changed, the corrsponding plugins
+    // need to be re-instanciated.
     if ($offset == 'widget') {
       unset($this->widget);
     }
+    if ($offset == 'display') {
+      unset($this->formatters);
+    }
   }
 
   /**
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
new file mode 100644
index 0000000..6d9a225
--- /dev/null
+++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterBase.php
@@ -0,0 +1,128 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\field\Plugin\Type\Formatter\FormatterBase.
+ */
+
+namespace Drupal\field\Plugin\Type\Formatter;
+
+use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\field\Plugin\PluginSettingsBase;
+use Drupal\field\FieldInstance;
+
+/**
+ * Base class for 'Field formatter' plugin implementations.
+ */
+abstract class FormatterBase extends PluginSettingsBase implements FormatterInterface {
+
+  /**
+   * The field definition.
+   *
+   * @var array
+   */
+  protected $field;
+
+  /**
+   * The field instance definition.
+   *
+   * @var Drupal\field\FieldInstance
+   */
+  protected $instance;
+
+  /**
+   * The formatter settings.
+   *
+   * @var array
+   */
+  protected $settings;
+
+  /**
+   * The formatter weight.
+   *
+   * @var int
+   */
+  protected $weight;
+
+  /**
+   * The label display setting.
+   *
+   * @var string
+   */
+  protected $label;
+
+  /**
+   * The view mode.
+   *
+   * @var string
+   */
+  protected $viewMode;
+
+  public function __construct($plugin_id, DiscoveryInterface $discovery, $instance, array $settings, $weight, $label, $view_mode) {
+    parent::__construct(array(), $plugin_id, $discovery);
+
+    $this->instance = $instance;
+    $this->field = field_info_field($instance['field_name']);
+    $this->settings = $settings;
+    $this->weight = $weight;
+    $this->label = $label;
+    $this->viewMode = $view_mode;
+  }
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::view().
+   */
+  public function view(EntityInterface $entity, $langcode, array $items) {
+    $field = $this->field;
+    $instance = $this->instance;
+
+    $addition = array();
+
+    $elements = $this->viewElements($entity, $langcode, $items);
+    if ($elements) {
+      $entity_type = $entity->entityType();
+      $info = array(
+        '#theme' => 'field',
+        '#weight' => $this->weight,
+        '#title' => $instance['label'],
+        '#access' => field_access($field, 'view', $entity_type, $entity),
+        '#label_display' => $this->label,
+        '#view_mode' => $this->viewMode,
+        '#language' => $langcode,
+        '#field_name' => $field['field_name'],
+        '#field_type' => $field['type'],
+        '#field_translatable' => $field['translatable'],
+        '#entity_type' => $entity_type,
+        '#bundle' => $entity->bundle(),
+        '#object' => $entity,
+        '#items' => $items,
+        '#formatter' => $this->getPluginId(),
+      );
+
+      $addition[$field['field_name']] = array_merge($info, $elements);
+    }
+
+    return $addition;
+  }
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsForm().
+   */
+  public function settingsForm(array $form, array &$form_state) {
+    return array();
+  }
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsSummary().
+   */
+  public function settingsSummary() {
+    return '';
+  }
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::prepareView().
+   */
+  public function prepareView(array $entities, $langcode, array &$items) { }
+
+}
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterInterface.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterInterface.php
new file mode 100644
index 0000000..35f342d
--- /dev/null
+++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterInterface.php
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\field\Plugin\Type\Formatter\FormatterInterface.
+ */
+
+namespace Drupal\field\Plugin\Type\Formatter;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\field\FieldInstance;
+use Drupal\field\Plugin\PluginSettingsInterface;
+
+/**
+ * Interface definition for field widget plugins.
+ */
+interface FormatterInterface extends PluginSettingsInterface {
+
+  /**
+   * Returns a form to configure settings for the formatter.
+   *
+   * Invoked from field_ui_field_edit_form() to allow administrators to
+   * configure the formatter. The field_ui module takes care of handling 
+   * submitted form values.
+   *
+   * @param array $form
+   *   The form where the settings form is being included in.
+   * @param array $form_state
+   *   An associative array containing the current state of the form.
+   *
+   * @return array
+   *   The form elements for the formatter settings.
+   */
+  public function settingsForm(array $form, array &$form_state);
+
+  /**
+   * Return a short summary for the current formatter settings.
+   *
+   * If an empty result is returned, the formatter is assumed to have no
+   * configurable settings, and no UI will be provided to display a settings
+   * form.
+   *
+   * @return string
+   *   A short summary of the formatter settings.
+   */
+  public function settingsSummary();
+
+  /**
+   * Allow formatters to load information for field values being displayed.
+   *
+   * This should be used when a formatter needs to load additional information
+   * from the database in order to render a field, for example a reference field
+   * which displays properties of the referenced entities such as name or type.
+   *
+   * This method is called after the field type's implementation of
+   * hook_field_prepare_view().
+   *
+   * 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.
+   *
+   * @param array $entities
+   *   Array of entities being displayed, keyed by entity ID.
+   * @param string $langcode
+   *   The language the field values are to be shown in. If no language is
+   *   provided the current language is used.
+   * @param array $items
+   *   Array of field values for the entities, keyed by entity ID.
+   */
+  public function prepareView(array $entities, $langcode, array &$items);
+
+  /**
+   * Builds a renderable array for one field on one entity instance.
+   *
+   * @param Drupal\Core\Entity\EntityInterface $entity
+   *   A single object of type $entity_type.
+   * @param string $langcode
+   *   The language associated to $items.
+   * @param array $items
+   *   Array of field values already loaded for the entities, keyed by entity id.
+   *
+   * @return array
+   *   A renderable array for a themed field with its label and all its values.
+   */
+  public function view(EntityInterface $entity, $langcode, array $items);
+
+  /**
+   * Builds a renderable array for a field value.
+   *
+   * @param Drupal\Core\Entity\EntityInterface $entity
+   *   The entity being displayed.
+   * @param string $langcode
+   *   The language associated with $items.
+   * @param array $items
+   *   Array of values for this field.
+   *
+   * @return array
+   *   A renderable array for the $items, as an array of child elements keyed
+   *   by numeric indexes starting from 0.
+   */
+  public function viewElements(EntityInterface $entity, $langcode, array $items);
+
+}
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterLegacyDiscoveryDecorator.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterLegacyDiscoveryDecorator.php
new file mode 100644
index 0000000..afb3078
--- /dev/null
+++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterLegacyDiscoveryDecorator.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\field\Plugin\Type\Formatter\FormatterLegacyDiscoveryDecorator.
+ */
+
+namespace Drupal\field\Plugin\Type\Formatter;
+
+use Drupal\field\Plugin\Type\LegacyDiscoveryDecorator;
+
+/**
+ * Custom decorator to add legacy widgets.
+ *
+ * Legacy widgets are discovered through the old
+ * hook_field_formatter_info() hook and handled by the
+ * Drupal\field\Plugin\field\formatter\LegacyFormatter class.
+ *
+ * @todo Remove once all core formatters have been converted.
+ */
+class FormatterLegacyDiscoveryDecorator extends LegacyDiscoveryDecorator {
+
+  protected $hook = 'field_formatter_info';
+
+  public function processDefinition(array &$definition) {
+    $definition['class'] = '\Drupal\field\Plugin\field\formatter\LegacyFormatter';
+
+    // Transform properties for which the format has changed.
+    if (isset($definition['field types'])) {
+      $definition['field_types'] = $definition['field types'];
+      unset($definition['field types']);
+    }
+  }
+
+}
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetPluginManager.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php
similarity index 70%
copy from core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetPluginManager.php
copy to core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php
index 5b3045d..e1eb076 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetPluginManager.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php
@@ -2,35 +2,34 @@
 
 /**
  * @file
- * Definition of Drupal\field\Plugin\Type\Widget\WidgetPluginManager.
+ * Definition of Drupal\field\Plugin\Type\Formatter\FormatterPluginManager.
  */
 
-namespace Drupal\field\Plugin\Type\Widget;
+namespace Drupal\field\Plugin\Type\Formatter;
 
 use Drupal\Component\Plugin\PluginManagerBase;
 use Drupal\Core\Plugin\Discovery\CacheDecorator;
 use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
-use Drupal\field\Plugin\Type\LegacyDiscoveryDecorator;
+use Drupal\field\Plugin\Type\Formatter\FormatterLegacyDiscoveryDecorator;
+use Drupal\Component\Plugin\Factory\ReflectionFactory;
 
 /**
- * Plugin type manager for field widgets.
+ * Plugin type manager for field formatters.
  */
-class WidgetPluginManager extends PluginManagerBase {
+class FormatterPluginManager extends PluginManagerBase {
 
   protected $defaults = array(
     'settings' => array(),
-    'multiple_values' => FALSE,
   );
 
   protected $cache_bin = 'field';
-  protected $cache_key = 'field_widget_types';
-  protected $hook = 'field_widget_info';
+  protected $cache_key = 'field_formatter_types';
 
   public function __construct() {
-    $this->baseDiscovery = new LegacyDiscoveryDecorator(new AnnotatedClassDiscovery('field', 'widget'));
+    $this->baseDiscovery = new FormatterLegacyDiscoveryDecorator(new AnnotatedClassDiscovery('field', 'formatter'));
     $this->discovery = new CacheDecorator($this->baseDiscovery, $this->cache_key, $this->cache_bin);
 
-    $this->factory = new WidgetFactory($this);
+    $this->factory = new ReflectionFactory($this);
   }
 
   /**
@@ -58,7 +57,7 @@ class WidgetPluginManager extends PluginManagerBase {
   }
 
   /**
-   * Overrides Drupal\Component\Plugin\PluginManagerBase::getInstance().
+   * Overrides PluginManagerBase::getInstance().
    */
   public function getInstance(array $options) {
     $instance = $options['instance'];
@@ -67,19 +66,21 @@ class WidgetPluginManager extends PluginManagerBase {
     $definition = $this->getDefinition($type);
     $field = field_info_field($instance['field_name']);
 
-    // Switch back to default widget if either:
+    // Switch back to default formatter if either:
     // - $type_info doesn't exist (the widget type is unknown),
     // - the field type is not allowed for the widget.
     if (!isset($definition['class']) || !in_array($field['type'], $definition['field_types'])) {
       // Grab the default widget for the field type.
       $field_type_definition = field_info_field_types($field['type']);
-      $type = $field_type_definition['default_widget'];
+      $type = $field_type_definition['default_formatter'];
     }
 
     $configuration = array(
       'instance' => $instance,
       'settings' => $options['settings'],
       'weight' => $options['weight'],
+      'label' => $options['label'],
+      'view_mode' => $options['view_mode'],
     );
     return $this->createInstance($type, $configuration);
   }
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/LegacyDiscoveryDecorator.php b/core/modules/field/lib/Drupal/field/Plugin/Type/LegacyDiscoveryDecorator.php
index 7cfb993..06c715e 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/Type/LegacyDiscoveryDecorator.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/Type/LegacyDiscoveryDecorator.php
@@ -18,7 +18,9 @@ use Drupal\Core\Plugin\Discovery\HookDiscovery;
  *
  * @todo Remove once all core widgets have been converted.
  */
-class LegacyDiscoveryDecorator implements DiscoveryInterface {
+abstract class LegacyDiscoveryDecorator implements DiscoveryInterface {
+
+  protected $hook;
 
   /**
    * The decorated discovery object.
@@ -52,21 +54,10 @@ class LegacyDiscoveryDecorator implements DiscoveryInterface {
   public function getDefinitions() {
     $definitions = $this->decorated->getDefinitions();
 
-    $legacy_discovery = new HookDiscovery('field_widget_info');
+    $legacy_discovery = new HookDiscovery($this->hook);
     if ($legacy_definitions = $legacy_discovery->getDefinitions()) {
-      foreach ($legacy_definitions as $plugin_id => &$definition) {
-        $definition['class'] = '\Drupal\field\Plugin\field\widget\LegacyWidget';
-
-        // Transform properties for which the format has changed.
-        if (isset($definition['field types'])) {
-          $definition['field_types'] = $definition['field types'];
-          unset($definition['field types']);
-        }
-        if (isset($definition['behaviors']['multiple values'])) {
-          $definition['multiple_values'] = $definition['behaviors']['multiple values'];
-          unset($definition['behaviors']['multiple values']);
-        }
-
+      foreach ($legacy_definitions as $plugin_id => $definition) {
+        $this->processDefinition($definition);
         $definitions[$plugin_id] = $definition;
       }
     }
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetLegacyDiscoveryDecorator.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetLegacyDiscoveryDecorator.php
new file mode 100644
index 0000000..dd7c645
--- /dev/null
+++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetLegacyDiscoveryDecorator.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\field\Plugin\Type\Widget\WidgetLegacyDiscoveryDecorator.
+ */
+
+namespace Drupal\field\Plugin\Type\Widget;
+
+use Drupal\field\Plugin\Type\LegacyDiscoveryDecorator;
+
+/**
+ * Custom decorator to add legacy widgets.
+ *
+ * Legacy widgets are discovered through the old hook_field_widget_info() hook,
+ * and handled by the Drupal\field\Plugin\field\widget\LegacyWidget class.
+ *
+ * @todo Remove once all core widgets have been converted.
+ */
+class WidgetLegacyDiscoveryDecorator extends LegacyDiscoveryDecorator {
+
+  protected $hook = 'field_widget_info';
+
+  public function processDefinition(array &$definition) {
+    $definition['class'] = '\Drupal\field\Plugin\field\widget\LegacyWidget';
+
+    // Transform properties for which the format has changed.
+    if (isset($definition['field types'])) {
+      $definition['field_types'] = $definition['field types'];
+      unset($definition['field types']);
+    }
+    if (isset($definition['behaviors']['multiple values'])) {
+      $definition['multiple_values'] = $definition['behaviors']['multiple values'];
+      unset($definition['behaviors']['multiple values']);
+    }
+  }
+
+}
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetPluginManager.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetPluginManager.php
index 5b3045d..f4da270 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetPluginManager.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetPluginManager.php
@@ -10,7 +10,7 @@ namespace Drupal\field\Plugin\Type\Widget;
 use Drupal\Component\Plugin\PluginManagerBase;
 use Drupal\Core\Plugin\Discovery\CacheDecorator;
 use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
-use Drupal\field\Plugin\Type\LegacyDiscoveryDecorator;
+use Drupal\field\Plugin\Type\Widget\WidgetLegacyDiscoveryDecorator;
 
 /**
  * Plugin type manager for field widgets.
@@ -27,7 +27,7 @@ class WidgetPluginManager extends PluginManagerBase {
   protected $hook = 'field_widget_info';
 
   public function __construct() {
-    $this->baseDiscovery = new LegacyDiscoveryDecorator(new AnnotatedClassDiscovery('field', 'widget'));
+    $this->baseDiscovery = new WidgetLegacyDiscoveryDecorator(new AnnotatedClassDiscovery('field', 'widget'));
     $this->discovery = new CacheDecorator($this->baseDiscovery, $this->cache_key, $this->cache_bin);
 
     $this->factory = new WidgetFactory($this);
diff --git a/core/modules/field/lib/Drupal/field/Plugin/field/formatter/LegacyFormatter.php b/core/modules/field/lib/Drupal/field/Plugin/field/formatter/LegacyFormatter.php
new file mode 100644
index 0000000..4f2c4f5
--- /dev/null
+++ b/core/modules/field/lib/Drupal/field/Plugin/field/formatter/LegacyFormatter.php
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\field\Plugin\field\formatter\LegacyFormatter.
+ */
+
+namespace Drupal\field\Plugin\field\formatter;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\field\Plugin\Type\Formatter\FormatterBase;
+
+/**
+ * Plugin implementation for legacy formatters.
+ *
+ * This special implementation acts as a temporary BC layer for formatters that
+ * have not been converted to Plugins, and bridges new methods to the old-style
+ * hook_field_formatter_*() callbacks.
+ *
+ * This class is not discovered by the annotations reader, but referenced by
+ * the Drupal\field\Plugin\Discovery\LegacyDiscoveryDecorator.
+ *
+ * @todo Remove once all core formatters have been converted.
+ */
+class LegacyFormatter extends FormatterBase {
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsForm().
+   */
+  public function settingsForm(array $form, array &$form_state) {
+    $definition = $this->getDefinition();
+    $function = $definition['module'] . '_field_formatter_settings_form';
+
+    // hook_field_formatter_settings_form() implementations read display
+    // properties directly from $instance. Put the actual properties we use
+    // here.
+    $instance = clone $this->instance;
+    $instance['display'][$this->viewMode] = array(
+      'type' => $this->getPluginId(),
+      'settings' => $this->getSettings(),
+      'weight' => $this->weight,
+      'label' => $this->label,
+    );
+
+    if (function_exists($function)) {
+      return $function($this->field, $instance, $this->viewMode, $form, $form_state);
+    }
+    return array();
+  }
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsSummary().
+   */
+  public function settingsSummary() {
+    $definition = $this->getDefinition();
+    $function = $definition['module'] . '_field_formatter_settings_summary';
+
+    // hook_field_formatter_settings_summary() implementations read display
+    // properties directly from $instance. Put the actual properties we use
+    // here.
+    $instance = clone $this->instance;
+    $instance['display'][$this->viewMode] = array(
+      'type' => $this->getPluginId(),
+      'settings' => $this->getSettings(),
+      'weight' => $this->weight,
+      'label' => $this->label,
+    );
+
+    if (function_exists($function)) {
+      return $function($this->field, $instance, $this->viewMode);
+    }
+  }
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::prepareView().
+   */
+  public function prepareView(array $entities, $langcode, array &$items) {
+    $definition = $this->getDefinition();
+    $function = $definition['module'] . '_field_formatter_prepare_view';
+    if (function_exists($function)) {
+      // Grab the entity type from the first entity.
+      $entity = current($entities);
+      $entity_type = $entity->entityType();
+
+      // hook_field_formatter_prepare_view() received an array of display properties,
+      // for each entity (the same hook could end up being called for different formatters,
+      // since one hook implementation could provide several formatters).
+      $display = array(
+        'type' => $this->getPluginId(),
+        'settings' => $this->getSettings(),
+        'weight' => $this->weight,
+        'label' => $this->label,
+      );
+      $displays = array();
+      foreach ($entities as $entity) {
+        $displays[$entity->id()] = $display;
+      }
+
+      $function($entity_type, $entities, $this->field, $this->instance, $langcode, $items, $displays);
+    }
+  }
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::viewElements().
+   */
+  public function viewElements(EntityInterface $entity, $langcode, array $items) {
+    $definition = $this->getDefinition();
+    $function = $definition['module'] . '_field_formatter_view';
+    if (function_exists($function)) {
+      // hook_field_formatter_view() received an array of display properties,
+      $display = array(
+        'type' => $this->getPluginId(),
+        'settings' => $this->getSettings(),
+        'weight' => $this->weight,
+        'label' => $this->label,
+      );
+
+      return $function($entity->entityType(), $entity, $this->field, $this->instance, $langcode, $items, $display);
+    }
+  }
+
+}
diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php
index 28112d2..6b1f7d8 100644
--- a/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php
@@ -43,15 +43,6 @@ class FieldInfoTest extends FieldTestBase {
       $this->assertEqual($info[$t_key]['module'], 'field_test',  t("Field type field_test module appears"));
     }
 
-    $formatter_info = field_test_field_formatter_info();
-    $info = field_info_formatter_types();
-    foreach ($formatter_info as $f_key => $formatter) {
-      foreach ($formatter as $key => $val) {
-        $this->assertEqual($info[$f_key][$key], $val, t("Formatter type $f_key key $key is $val"));
-      }
-      $this->assertEqual($info[$f_key]['module'], 'field_test',  t("Formatter type field_test module appears"));
-    }
-
     $storage_info = field_test_field_storage_info();
     $info = field_info_storage_types();
     foreach ($storage_info as $s_key => $storage) {
@@ -205,10 +196,10 @@ class FieldInfoTest extends FieldTestBase {
     $this->assertIdentical($widget->getSettings(), $widget_type['settings'] , t('All expected widget settings are present.'));
 
     // Check that display settings are set for the 'default' mode.
-    $display = $instance['display']['default'];
-    $this->assertIdentical($display['type'], $field_type['default_formatter'], t("Formatter is set for the 'default' view mode"));
-    $formatter_type = field_info_formatter_types($display['type']);
-    $this->assertIdentical($display['settings'], $formatter_type['settings'] , t("Formatter settings are set for the 'default' view mode"));
+    $formatter = $instance->getFormatter('default');
+    $this->assertIdentical($formatter->getPluginId(), $field_type['default_formatter'], t("Formatter is set for the 'default' view mode"));
+    $formatter_type = $formatter->getDefinition();
+    $this->assertIdentical($formatter->getSettings(), $formatter_type['settings'] , t("Formatter settings are set for the 'default' view mode"));
   }
 
   /**
diff --git a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextDefaultFormatter.php b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextDefaultFormatter.php
new file mode 100644
index 0000000..2315e38
--- /dev/null
+++ b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextDefaultFormatter.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\text\Plugin\field\formatter\TextDefaultFormatter.
+ */
+
+namespace Drupal\text\Plugin\field\formatter;
+
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\field\Plugin\Type\Formatter\FormatterBase;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Plugin implementation of the 'text_default' formatter.
+ *
+ * @Plugin(
+ *   id = "text_default",
+ *   module = "text",
+ *   label = @Translation("Default"),
+ *   field_types = {
+ *     "text",
+ *     "text_long",
+ *     "text_with_summary"
+ *   }
+ * )
+ */
+class TextDefaultFormatter extends FormatterBase {
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::viewElements().
+   */
+  public function viewElements(EntityInterface $entity, $langcode, array $items) {
+    $elements = array();
+
+    foreach ($items as $delta => $item) {
+      $output = _text_sanitize($this->instance, $langcode, $item, 'value');
+      $elements[$delta] = array('#markup' => $output);
+    }
+
+    return $elements;
+  }
+}
diff --git a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextPlainFormatter.php b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextPlainFormatter.php
new file mode 100644
index 0000000..588f2cc
--- /dev/null
+++ b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextPlainFormatter.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\text\Plugin\field\formatter\TextPlainFormatter.
+ */
+
+namespace Drupal\text\Plugin\field\formatter;
+
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\field\Plugin\Type\Formatter\FormatterBase;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Plugin implementation of the 'text_plain' formatter.
+ *
+ * @Plugin(
+ *   id = "text_plain",
+ *   module = "text",
+ *   label = @Translation("Plain text"),
+ *   field_types = {
+ *     "text",
+ *     "text_long",
+ *     "text_with_summary"
+ *   }
+ * )
+ */
+class TextPlainFormatter extends FormatterBase {
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::viewElements().
+   */
+  public function viewElements(EntityInterface $entity, $langcode, array $items) {
+    $elements = array();
+
+    foreach ($items as $delta => $item) {
+      $elements[$delta] = array('#markup' => strip_tags($item['value']));
+    }
+
+    return $elements;
+  }
+}
diff --git a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextSummaryOrTrimmedFormatter.php b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextSummaryOrTrimmedFormatter.php
new file mode 100644
index 0000000..6c012d9
--- /dev/null
+++ b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextSummaryOrTrimmedFormatter.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ *
+ * Definition of Drupal\text\Plugin\field\formatter\TextSummaryOrTrimmedFormatter.
+ */
+namespace Drupal\text\Plugin\field\formatter;
+
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * Plugin implementation of the 'text_summary_or_trimmed' formatter.
+ *
+ * @Plugin(
+ *   id = "text_summary_or_trimmed",
+ *   module = "text",
+ *   label = @Translation("Summary or trimmed"),
+ *   field_types = {
+ *     "text_with_summary"
+ *   },
+ *   settings = {
+ *     "trim_length" = "600"
+ *   }
+ * )
+ */
+class TextSummaryOrTrimmedFormatter extends TextTrimmedFormatter { }
+
diff --git a/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextTrimmedFormatter.php b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextTrimmedFormatter.php
new file mode 100644
index 0000000..c69479c
--- /dev/null
+++ b/core/modules/field/modules/text/lib/Drupal/text/Plugin/field/formatter/TextTrimmedFormatter.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ *
+ * Definition of Drupal\text\Plugin\field\formatter\TextTrimmedFormatter.
+ */
+namespace Drupal\text\Plugin\field\formatter;
+
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\field\Plugin\Type\Formatter\FormatterBase;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Plugin implementation of the 'text_trimmed'' formatter.
+ *
+ * Note: This class also contains the implementations used by the
+ * 'text_summary_or_trimmed' formatter.
+ *
+ * @see Drupal\text\Field\Formatter\TextSummaryOrTrimmedFormatter
+ *
+ * @Plugin(
+ *   id = "text_trimmed",
+ *   module = "text",
+ *   label = @Translation("Trimmed"),
+ *   field_types = {
+ *     "text",
+ *     "text_long",
+ *     "text_with_summary"
+ *   },
+ *   settings = {
+ *     "trim_length" = "600"
+ *   }
+ * )
+ */
+class TextTrimmedFormatter extends FormatterBase {
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsForm().
+   */
+  public function settingsForm(array $form, array &$form_state) {
+    $element['trim_length'] = array(
+      '#title' => t('Trim length'),
+      '#type' => 'number',
+      '#default_value' => $this->getSetting('trim_length'),
+      '#min' => 1,
+      '#required' => TRUE,
+    );
+    return $element;
+  }
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsSummary().
+   */
+  public function settingsSummary() {
+    return t('Trim length') . ': ' . $this->getSetting('trim_length');
+  }
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::viewElements().
+   */
+  public function viewElements(EntityInterface $entity, $langcode, array $items) {
+    $elements = array();
+
+    foreach ($items as $delta => $item) {
+      if ($this->getPluginId() == 'text_summary_or_trimmed' && !empty($item['summary'])) {
+        $output = _text_sanitize($this->instance, $langcode, $item, 'summary');
+      }
+      else {
+        $output = _text_sanitize($this->instance, $langcode, $item, 'value');
+        $output = text_summary($output, $this->instance['settings']['text_processing'] ? $item['format'] : NULL, $this->getSetting('trim_length'));
+      }
+      $elements[$delta] = array('#markup' => $output);
+    }
+
+    return $elements;
+  }
+}
diff --git a/core/modules/field/modules/text/text.module b/core/modules/field/modules/text/text.module
index bbd209e..00a2fd8 100644
--- a/core/modules/field/modules/text/text.module
+++ b/core/modules/field/modules/text/text.module
@@ -147,8 +147,6 @@ function text_field_validate($entity_type, $entity, $field, $instance, $langcode
  * Where possible, generate the sanitized version of each field early so that
  * it is cached in the field cache. This avoids looking up from the filter cache
  * separately.
- *
- * @see text_field_formatter_view()
  */
 function text_field_load($entity_type, $entities, $field, $instances, $langcode, &$items) {
   foreach ($entities as $id => $entity) {
@@ -176,122 +174,6 @@ function text_field_is_empty($item, $field) {
 }
 
 /**
- * Implements hook_field_formatter_info().
- */
-function text_field_formatter_info() {
-  return array(
-    'text_default' => array(
-      'label' => t('Default'),
-      'field types' => array('text', 'text_long', 'text_with_summary'),
-    ),
-    'text_plain' => array(
-      'label' => t('Plain text'),
-      'field types' => array('text', 'text_long', 'text_with_summary'),
-    ),
-
-    // The text_trimmed formatter displays the trimmed version of the
-    // full element of the field. It is intended to be used with text
-    // and text_long fields. It also works with text_with_summary
-    // fields though the text_summary_or_trimmed formatter makes more
-    // sense for that field type.
-    'text_trimmed' => array(
-      'label' => t('Trimmed'),
-      'field types' => array('text', 'text_long', 'text_with_summary'),
-      'settings' => array('trim_length' => 600),
-    ),
-
-    // The 'summary or trimmed' field formatter for text_with_summary
-    // fields displays returns the summary element of the field or, if
-    // the summary is empty, the trimmed version of the full element
-    // of the field.
-    'text_summary_or_trimmed' => array(
-      'label' => t('Summary or trimmed'),
-      'field types' => array('text_with_summary'),
-      'settings' => array('trim_length' => 600),
-    ),
-  );
-}
-
-/**
- * Implements hook_field_formatter_settings_form().
- */
-function text_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
-  $display = $instance['display'][$view_mode];
-  $settings = $display['settings'];
-
-  $element = array();
-
-  if (strpos($display['type'], '_trimmed') !== FALSE) {
-    $element['trim_length'] = array(
-      '#title' => t('Trim length'),
-      '#type' => 'number',
-      '#default_value' => $settings['trim_length'],
-      '#min' => 1,
-      '#required' => TRUE,
-    );
-  }
-
-  return $element;
-}
-
-/**
- * Implements hook_field_formatter_settings_summary().
- */
-function text_field_formatter_settings_summary($field, $instance, $view_mode) {
-  $display = $instance['display'][$view_mode];
-  $settings = $display['settings'];
-
-  $summary = '';
-
-  if (strpos($display['type'], '_trimmed') !== FALSE) {
-    $summary = t('Trim length') . ': ' . $settings['trim_length'];
-  }
-
-  return $summary;
-}
-
-/**
- * Implements hook_field_formatter_view().
- */
-function text_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
-  $element = array();
-
-  switch ($display['type']) {
-    case 'text_default':
-    case 'text_trimmed':
-      foreach ($items as $delta => $item) {
-        $output = _text_sanitize($instance, $langcode, $item, 'value');
-        if ($display['type'] == 'text_trimmed') {
-          $output = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $display['settings']['trim_length']);
-        }
-        $element[$delta] = array('#markup' => $output);
-      }
-      break;
-
-    case 'text_summary_or_trimmed':
-      foreach ($items as $delta => $item) {
-        if (!empty($item['summary'])) {
-          $output = _text_sanitize($instance, $langcode, $item, 'summary');
-        }
-        else {
-          $output = _text_sanitize($instance, $langcode, $item, 'value');
-          $output = text_summary($output, $instance['settings']['text_processing'] ? $item['format'] : NULL, $display['settings']['trim_length']);
-        }
-        $element[$delta] = array('#markup' => $output);
-      }
-      break;
-
-    case 'text_plain':
-      foreach ($items as $delta => $item) {
-        $element[$delta] = array('#markup' => strip_tags($item['value']));
-      }
-      break;
-  }
-
-  return $element;
-}
-
-/**
  * Sanitizes the 'value' or 'summary' data of a text value.
  *
  * Depending on whether the field instance uses text processing, data is run
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 b2ed35c..44d22c1 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
@@ -175,144 +175,6 @@ function field_test_widget_multiple_validate($element, &$form_state) {
 }
 
 /**
- * Implements hook_field_formatter_info().
- */
-function field_test_field_formatter_info() {
-  return array(
-    'field_test_default' => array(
-      'label' => t('Default'),
-      'description' => t('Default formatter'),
-      'field types' => array('test_field'),
-      'settings' => array(
-        'test_formatter_setting' => 'dummy test string',
-      ),
-    ),
-    'field_test_multiple' => array(
-      'label' => t('Multiple'),
-      'description' => t('Multiple formatter'),
-      'field types' => array('test_field'),
-      'settings' => array(
-        'test_formatter_setting_multiple' => 'dummy test string',
-      ),
-    ),
-    'field_test_with_prepare_view' => array(
-      'label' => t('Tests hook_field_formatter_prepare_view()'),
-      'field types' => array('test_field'),
-      'settings' => array(
-        'test_formatter_setting_additional' => 'dummy test string',
-      ),
-    ),
-  );
-}
-
-/**
- * Implements hook_field_formatter_settings_form().
- */
-function field_test_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
-  $display = $instance['display'][$view_mode];
-  $settings = $display['settings'];
-
-  $element = array();
-
-  // The name of the setting depends on the formatter type.
-  $map = array(
-    'field_test_default' => 'test_formatter_setting',
-    'field_test_multiple' => 'test_formatter_setting_multiple',
-    'field_test_with_prepare_view' => 'test_formatter_setting_additional',
-  );
-
-  if (isset($map[$display['type']])) {
-    $name = $map[$display['type']];
-
-    $element[$name] = array(
-      '#title' => t('Setting'),
-      '#type' => 'textfield',
-      '#size' => 20,
-      '#default_value' => $settings[$name],
-      '#required' => TRUE,
-    );
-  }
-
-  return $element;
-}
-
-/**
- * Implements hook_field_formatter_settings_summary().
- */
-function field_test_field_formatter_settings_summary($field, $instance, $view_mode) {
-  $display = $instance['display'][$view_mode];
-  $settings = $display['settings'];
-
-  $summary = '';
-
-  // The name of the setting depends on the formatter type.
-  $map = array(
-    'field_test_default' => 'test_formatter_setting',
-    'field_test_multiple' => 'test_formatter_setting_multiple',
-    'field_test_with_prepare_view' => 'test_formatter_setting_additional',
-  );
-
-  if (isset($map[$display['type']])) {
-    $name = $map[$display['type']];
-    $summary = t('@setting: @value', array('@setting' => $name, '@value' => $settings[$name]));
-  }
-
-  return $summary;
-}
-
-/**
- * Implements hook_field_formatter_prepare_view().
- */
-function field_test_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
-  foreach ($items as $id => $item) {
-    // To keep the test non-intrusive, only act on the
-    // 'field_test_with_prepare_view' formatter.
-    if ($displays[$id]['type'] == 'field_test_with_prepare_view') {
-      foreach ($item as $delta => $value) {
-        // Don't add anything on empty values.
-        if ($value) {
-          $items[$id][$delta]['additional_formatter_value'] = $value['value'] + 1;
-        }
-      }
-    }
-  }
-}
-
-/**
- * Implements hook_field_formatter_view().
- */
-function field_test_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
-  $element = array();
-  $settings = $display['settings'];
-
-  switch ($display['type']) {
-    case 'field_test_default':
-      foreach ($items as $delta => $item) {
-        $element[$delta] = array('#markup' => $settings['test_formatter_setting'] . '|' . $item['value']);
-      }
-      break;
-
-    case 'field_test_with_prepare_view':
-      foreach ($items as $delta => $item) {
-        $element[$delta] = array('#markup' => $settings['test_formatter_setting_additional'] . '|' . $item['value'] . '|' . $item['additional_formatter_value']);
-      }
-      break;
-
-    case 'field_test_multiple':
-      if (!empty($items)) {
-        $array = array();
-        foreach ($items as $delta => $item) {
-          $array[] = $delta . ':' . $item['value'];
-        }
-        $element[0] = array('#markup' => $settings['test_formatter_setting_multiple'] . '|' . implode('|', $array));
-      }
-      break;
-  }
-
-  return $element;
-}
-
-/**
  * Sample 'default value' callback.
  */
 function field_test_default_value($entity_type, $entity, $field, $instance) {
diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldDefaultFormatter.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldDefaultFormatter.php
new file mode 100644
index 0000000..b7d08d5
--- /dev/null
+++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldDefaultFormatter.php
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\field_test\Plugin\field\formatter\TestFieldDefaultFormatter.
+ */
+
+namespace Drupal\field_test\Plugin\field\formatter;
+
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\field\Plugin\Type\Formatter\FormatterBase;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Plugin implementation of the 'field_test_default' formatter.
+ *
+ * @Plugin(
+ *   id = "field_test_default",
+ *   module = "field_test",
+ *   label = @Translation("Default"),
+ *   description = @Translation("Default formatter"),
+ *   field_types = {
+ *     "test_field"
+ *   },
+ *   settings = {
+ *     "test_formatter_setting" = "dummy test string"
+ *   }
+ * )
+ */
+class TestFieldDefaultFormatter extends FormatterBase {
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsForm().
+   */
+  public function settingsForm(array $form, array &$form_state) {
+    $element['test_formatter_setting'] = array(
+      '#title' => t('Setting'),
+      '#type' => 'textfield',
+      '#size' => 20,
+      '#default_value' => $this->getSetting('test_formatter_setting'),
+      '#required' => TRUE,
+    );
+    return $element;
+  }
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsForm().
+   */
+  public function settingsSummary() {
+    return t('@setting: @value', array('@setting' => 'field_test_default', '@value' => $this->getSetting('test_formatter_setting')));
+  }
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::viewElements().
+   */
+  public function viewElements(EntityInterface $entity, $langcode, array $items) {
+    $elements = array();
+
+    foreach ($items as $delta => $item) {
+      $element[$delta] = array('#markup' => $this->getSetting('test_formatter_setting') . '|' . $item['value']);
+    }
+
+    return $elements;
+  }
+}
diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldMultipleFormatter.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldMultipleFormatter.php
new file mode 100644
index 0000000..92d9a30
--- /dev/null
+++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldMultipleFormatter.php
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\field_test\Plugin\field\formatter\TestFieldMultipleFormatter.
+ */
+
+namespace Drupal\field_test\Plugin\field\formatter;
+
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\field\Plugin\Type\Formatter\FormatterBase;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Plugin implementation of the 'field_test_multiple' formatter.
+ *
+ * @Plugin(
+ *   id = "field_test_multiple",
+ *   module = "field_test",
+ *   label = @Translation("Multiple"),
+ *   description = @Translation("Multiple formatter"),
+ *   field_types = {
+ *     "test_field"
+ *   },
+ *   settings = {
+ *     "test_formatter_setting_multiple" = "dummy test string"
+ *   }
+ * )
+ */
+class TestFieldMultipleFormatter extends FormatterBase {
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsForm().
+   */
+  public function settingsForm(array $form, array &$form_state) {
+    $element['test_formatter_setting_multiple'] = array(
+      '#title' => t('Setting'),
+      '#type' => 'textfield',
+      '#size' => 20,
+      '#default_value' => $this->getSetting('test_formatter_setting_multiple'),
+      '#required' => TRUE,
+    );
+    return $element;
+  }
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsForm().
+   */
+  public function settingsSummary() {
+    return t('@setting: @value', array('@setting' => 'field_test_multiple', '@value' => $this->getSetting('test_formatter_setting_multiple')));
+  }
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::viewElements().
+   */
+  public function viewElements(EntityInterface $entity, $langcode, array $items) {
+    $elements = array();
+
+    if (!empty($items)) {
+      $array = array();
+      foreach ($items as $delta => $item) {
+        $array[] = $delta . ':' . $item['value'];
+      }
+      $element[0] = array('#markup' => $this->getSetting('test_formatter_setting_multiple') . '|' . implode('|', $array));
+    }
+
+    return $elements;
+  }
+}
diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldPrepareViewFormatter.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldPrepareViewFormatter.php
new file mode 100644
index 0000000..32f3ec6
--- /dev/null
+++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/formatter/TestFieldPrepareViewFormatter.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\field_test\Plugin\field\formatter\TestFieldPrepareViewFormatter.
+ */
+
+namespace Drupal\field_test\Plugin\field\formatter;
+
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\field\Plugin\Type\Formatter\FormatterBase;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Plugin implementation of the 'field_test_with_prepare_view' formatter.
+ *
+ * @Plugin(
+ *   id = "field_test_with_prepare_view",
+ *   module = "field_test",
+ *   label = @Translation("With prepare step"),
+ *   description = @Translation("Tests prepareView() method"),
+ *   field_types = {
+ *     "test_field"
+ *   },
+ *   settings = {
+ *     "test_formatter_setting_additional" = "dummy test string"
+ *   }
+ * )
+ */
+class TestFieldPrepareViewFormatter extends FormatterBase {
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsForm().
+   */
+  public function settingsForm(array $form, array &$form_state) {
+    $element['test_formatter_setting_additional'] = array(
+      '#title' => t('Setting'),
+      '#type' => 'textfield',
+      '#size' => 20,
+      '#default_value' => $this->getSetting('test_formatter_setting_additional'),
+      '#required' => TRUE,
+    );
+    return $element;
+  }
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::settingsForm().
+   */
+  public function settingsSummary() {
+    return t('@setting: @value', array('@setting' => 'field_test_with_prepare_view', '@value' => $this->getSetting('test_formatter_setting_additional')));
+  }
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::prepareView().
+   */
+  public function prepareView(array $entities, $langcode, array &$items) {
+    foreach ($items as $id => $item) {
+      foreach ($item as $delta => $value) {
+        // Don't add anything on empty values.
+        if ($value) {
+          $items[$id][$delta]['additional_formatter_value'] = $value['value'] + 1;
+        }
+      }
+    }
+  }
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Formatter\FormatterInterface::viewElements().
+   */
+  public function viewElements(EntityInterface $entity, $langcode, array $items) {
+    $elements = array();
+
+    foreach ($items as $delta => $item) {
+      $element[$delta] = array('#markup' => $this->getSetting('test_formatter_setting_additional') . '|' . $item['value'] . '|' . $item['additional_formatter_value']);
+    }
+
+    return $elements;
+  }
+}
diff --git a/core/modules/field_ui/field_ui.admin.inc b/core/modules/field_ui/field_ui.admin.inc
index 54dc192..b023449 100644
--- a/core/modules/field_ui/field_ui.admin.inc
+++ b/core/modules/field_ui/field_ui.admin.inc
@@ -957,7 +957,20 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund
   // Field rows.
   foreach ($instances as $name => $instance) {
     $field = field_info_field($instance['field_name']);
-    $display = $instance['display'][$view_mode];
+
+    // Provide default settings if needed.
+    if (isset($instance['display'][$view_mode])) {
+      $display = $instance['display'][$view_mode];
+    }
+    else {
+      $display = array(
+        'type' => 'hidden',
+        'label' => 'above',
+        'settings' => array(),
+        'weight' => 0,
+      );
+    }
+
     $table[$name] = array(
       '#attributes' => array('class' => array('draggable', 'tabledrag-leaf')),
       '#row_type' => 'field',
@@ -1022,23 +1035,12 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund
     // Check the currently selected formatter, and merge persisted values for
     // formatter settings.
     if (isset($form_state['values']['fields'][$name]['type'])) {
-      $formatter_type = $form_state['values']['fields'][$name]['type'];
-    }
-    else {
-      $formatter_type = $display['type'];
+      $display['type'] = $form_state['values']['fields'][$name]['type'];
     }
     if (isset($form_state['formatter_settings'][$name])) {
-      $settings = $form_state['formatter_settings'][$name];
-    }
-    else {
-      $settings = $display['settings'];
+      $display['settings'] = $form_state['formatter_settings'][$name];
     }
-    $settings += field_info_formatter_settings($formatter_type);
-
-    $instance['display'][$view_mode]['type'] = $formatter_type;
-    $formatter = field_info_formatter_types($formatter_type);
-    $instance['display'][$view_mode]['module'] = $formatter['module'];
-    $instance['display'][$view_mode]['settings'] = $settings;
+    $formatter = $instance->getFormatter($display);
 
     // Base button element for the various formatter settings actions.
     $base_button = array(
@@ -1056,20 +1058,16 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund
       // settings form and submit buttons.
       $table[$name]['format']['settings_edit_form'] = array();
 
-      $settings_form = array();
-      $function = $formatter['module'] . '_field_formatter_settings_form';
-      if (function_exists($function)) {
-        $settings_form = $function($field, $instance, $view_mode, $form, $form_state);
-      }
+      if ($formatter && $settings_form = $formatter->settingsForm($form, $form_state)) {
+        $formatter_type_info = $formatter->getDefinition();
 
-      if ($settings_form) {
         $table[$name]['format']['#cell_attributes'] = array('colspan' => 3);
         $table[$name]['format']['settings_edit_form'] = array(
           '#type' => 'container',
           '#attributes' => array('class' => array('field-formatter-settings-edit-form')),
           '#parents' => array('fields', $name, 'settings_edit_form'),
           'label' => array(
-            '#markup' => t('Format settings:') . ' <span class="formatter-name">' . $formatter['label'] . '</span>',
+            '#markup' => t('Format settings:') . ' <span class="formatter-name">' . $formatter_type_info['label'] . '</span>',
           ),
           'settings' => $settings_form,
           'actions' => array(
@@ -1095,11 +1093,12 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund
       }
     }
     else {
-      // Display a summary of the current formatter settings.
-      $summary = module_invoke($formatter['module'], 'field_formatter_settings_summary', $field, $instance, $view_mode);
+      // Display a summary of the current formatter settings, and (if summary is
+      // not empty) a button to edit them.
       $table[$name]['settings_summary'] = array();
       $table[$name]['settings_edit'] = array();
-      if ($summary) {
+
+      if ($formatter && $summary = $formatter->settingsSummary()) {
         $table[$name]['settings_summary'] = array(
           '#markup' => '<div class="field-formatter-summary">' . $summary . '</div>',
           '#cell_attributes' => array('class' => array('field-formatter-summary-cell')),
@@ -1242,7 +1241,6 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund
   return $form;
 }
 
-
 /**
  * Form submission handler for buttons in field_ui_display_overview_form().
  */
@@ -1517,7 +1515,7 @@ function field_ui_formatter_options($field_type = NULL) {
     $field_types = field_info_field_types();
     $options = array();
     foreach (field_info_formatter_types() as $name => $formatter) {
-      foreach ($formatter['field types'] as $formatter_field_type) {
+      foreach ($formatter['field_types'] as $formatter_field_type) {
         // Check that the field type exists.
         if (isset($field_types[$formatter_field_type])) {
           $options[$formatter_field_type][$name] = $formatter['label'];
diff --git a/core/modules/field_ui/field_ui.api.php b/core/modules/field_ui/field_ui.api.php
index e33fbdd..0d37ac6 100644
--- a/core/modules/field_ui/field_ui.api.php
+++ b/core/modules/field_ui/field_ui.api.php
@@ -91,72 +91,5 @@ function hook_field_instance_settings_form($field, $instance) {
 }
 
 /**
- * Specify the form elements for a formatter's settings.
- *
- * @param $field
- *   The field structure being configured.
- * @param $instance
- *   The instance structure being configured.
- * @param $view_mode
- *   The view mode being configured.
- * @param $form
- *   The (entire) configuration form array, which will usually have no use here.
- * @param $form_state
- *   The form state of the (entire) configuration form.
- *
- * @return
- *   The form elements for the formatter settings.
- */
-function hook_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
-  $display = $instance['display'][$view_mode];
-  $settings = $display['settings'];
-
-  $element = array();
-
-  if ($display['type'] == 'text_trimmed' || $display['type'] == 'text_summary_or_trimmed') {
-    $element['trim_length'] = array(
-      '#title' => t('Length'),
-      '#type' => 'number',
-      '#default_value' => $settings['trim_length'],
-      '#min' => 1,
-      '#required' => TRUE,
-    );
-  }
-
-  return $element;
-
-}
-
-/**
- * Return a short summary for the current formatter settings of an instance.
- *
- * If an empty result is returned, the formatter is assumed to have no
- * configurable settings, and no UI will be provided to display a settings
- * form.
- *
- * @param $field
- *   The field structure.
- * @param $instance
- *   The instance structure.
- * @param $view_mode
- *   The view mode for which a settings summary is requested.
- *
- * @return
- *   A string containing a short summary of the formatter settings.
- */
-function hook_field_formatter_settings_summary($field, $instance, $view_mode) {
-  $display = $instance['display'][$view_mode];
-  $settings = $display['settings'];
-
-  $summary = '';
-
-  if ($display['type'] == 'text_trimmed' || $display['type'] == 'text_summary_or_trimmed') {
-    $summary = t('Length: @chars chars', array('@chars' => $settings['trim_length']));
-  }
-
-  return $summary;
-}
-
-/**
  * @} End of "addtogroup field_types".
  */
