diff --git a/core/lib/Drupal/Core/Entity/ContentEntityFormController.php b/core/lib/Drupal/Core/Entity/ContentEntityFormController.php
index 0383533..568d5cb 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityFormController.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityFormController.php
@@ -7,7 +7,8 @@
 
 namespace Drupal\Core\Entity;
 
-use Drupal\Core\Language\Language;
+use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
+use Drupal\entity\Entity\EntityFormDisplay;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -15,7 +16,7 @@
  *
  * @see \Drupal\Core\ContentEntityBase
  */
-class ContentEntityFormController extends EntityFormController {
+class ContentEntityFormController extends EntityFormController implements ContentEntityFormControllerInterface {
 
   /**
    * The entity manager.
@@ -47,16 +48,8 @@ public static function create(ContainerInterface $container) {
    * {@inheritdoc}
    */
   public function form(array $form, array &$form_state) {
-    $entity = $this->entity;
-    // @todo Exploit the Field API to generate the default widgets for the
-    // entity fields.
-    if ($entity->getEntityType()->isFieldable()) {
-      field_attach_form($entity, $form, $form_state, $this->getFormLangcode($form_state));
-    }
-
-    // Add a process callback so we can assign weights and hide extra fields.
-    $form['#process'][] = array($this, 'processForm');
-
+    $form = parent::form($form, $form_state);
+    $this->getFormDisplay($form_state)->buildForm($this->entity, $form, $form_state);
     return $form;
   }
 
@@ -66,27 +59,7 @@ public function form(array $form, array &$form_state) {
   public function validate(array $form, array &$form_state) {
     $this->updateFormLangcode($form_state);
     $entity = $this->buildEntity($form, $form_state);
-    $entity_type = $entity->getEntityTypeId();
-    $entity_langcode = $entity->language()->id;
-
-    $violations = array();
-    foreach ($entity as $field_name => $field) {
-      $field_violations = $field->validate();
-      if (count($field_violations)) {
-        $violations[$field_name] = $field_violations;
-      }
-    }
-
-    // Map errors back to form elements.
-    if ($violations) {
-      foreach ($violations as $field_name => $field_violations) {
-        $field_state = field_form_get_state($form['#parents'], $field_name, $form_state);
-        $field_state['constraint_violations'] = $field_violations;
-        field_form_set_state($form['#parents'], $field_name, $form_state, $field_state);
-      }
-
-      field_invoke_method('flagErrors', _field_invoke_widget_target($form_state['form_display']), $entity, $form, $form_state);
-    }
+    $this->getFormDisplay($form_state)->validateFormValues($entity, $form, $form_state);
 
     // @todo Remove this.
     // Execute legacy global validation handlers.
@@ -102,6 +75,10 @@ protected function init(array &$form_state) {
     // language.
     $langcode = $this->getFormLangcode($form_state);
     $this->entity = $this->entity->getTranslation($langcode);
+
+    $form_display = EntityFormDisplay::collectRenderDisplay($this->entity, $this->getOperation());
+    $this->setFormDisplay($form_display, $form_state);
+
     parent::init($form_state);
   }
 
@@ -128,37 +105,33 @@ public function isDefaultFormLangcode(array $form_state) {
   /**
    * {@inheritdoc}
    */
-  public function buildEntity(array $form, array &$form_state) {
-    $entity = clone $this->entity;
-    $entity_type_id = $entity->getEntityTypeId();
-    $entity_type = \Drupal::entityManager()->getDefinition($entity_type_id);
-
-    // @todo Exploit the Entity Field API to process the submitted field values.
-    // Copy top-level form values that are entity fields but not handled by
-    // field API without changing existing entity fields that are not being
-    // edited by this form. Values of fields handled by field API are copied
-    // by field_attach_extract_form_values() below.
-    $values_excluding_fields = $entity_type->isFieldable() ? array_diff_key($form_state['values'], field_info_instances($entity_type_id, $entity->bundle())) : $form_state['values'];
-    $definitions = $entity->getFieldDefinitions();
-
-    foreach ($values_excluding_fields as $key => $value) {
-      if (isset($definitions[$key])) {
-        $entity->$key = $value;
+  protected function copyFormValuesToEntity(EntityInterface $entity, array $form, array &$form_state) {
+    // First, extract values from widgets.
+    $extracted = $this->getFormDisplay($form_state)->extractFormValues($entity, $form, $form_state);
+
+    // Then extract the values of fields that are not rendered through widgets,
+    // by simply copying from top-level form values. This leaves the fields
+    // that are not being edited within this form untouched.
+    foreach ($form_state['values'] as $name => $values) {
+      if ($entity->hasField($name) && !isset($extracted[$name])) {
+        $entity->set($name, $values);
       }
     }
+  }
 
-    // Invoke all specified builders for copying form values to entity fields.
-    if (isset($form['#entity_builders'])) {
-      foreach ($form['#entity_builders'] as $function) {
-        call_user_func_array($function, array($entity_type_id, $entity, &$form, &$form_state));
-      }
-    }
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormDisplay(array $form_state) {
+    return isset($form_state['form_display']) ? $form_state['form_display'] : NULL;
+  }
 
-    // Invoke field API for copying field values.
-    if ($entity_type->isFieldable()) {
-      field_attach_extract_form_values($entity, $form, $form_state, array('langcode' => $this->getFormLangcode($form_state)));
-    }
-    return $entity;
+  /**
+   * {@inheritdoc}
+   */
+  public function setFormDisplay(EntityFormDisplayInterface $form_display, array &$form_state) {
+    $form_state['form_display'] = $form_display;
+    return $this;
   }
 
 }
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityFormControllerInterface.php b/core/lib/Drupal/Core/Entity/ContentEntityFormControllerInterface.php
new file mode 100644
index 0000000..77235d9
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/ContentEntityFormControllerInterface.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\ContentEntityFormControllerInterface.
+ */
+
+namespace Drupal\Core\Entity;
+
+use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
+
+/**
+ * Defines a common interface for content entity form controller classes.
+ */
+interface ContentEntityFormControllerInterface extends EntityFormControllerInterface {
+
+  /**
+   * Returns the form display.
+   *
+   * @param array $form_state
+   *   An associative array containing the current state of the form.
+   *
+   * @return \Drupal\Core\Entity\Display\EntityFormDisplayInterface.
+   *   The current form display.
+   */
+  public function getFormDisplay(array $form_state);
+
+  /**
+   * Sets the form display.
+   *
+   * Sets the form display which will be used for populating form element
+   * defaults.
+   *
+   * @param \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display
+   *   The form display that the current form operates with.
+   * @param array $form_state
+   *   An associative array containing the current state of the form.
+   */
+  public function setFormDisplay(EntityFormDisplayInterface $form_display, array &$form_state);
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Display/EntityFormDisplayInterface.php b/core/lib/Drupal/Core/Entity/Display/EntityFormDisplayInterface.php
index 1929d9a..28a5aa3 100644
--- a/core/lib/Drupal/Core/Entity/Display/EntityFormDisplayInterface.php
+++ b/core/lib/Drupal/Core/Entity/Display/EntityFormDisplayInterface.php
@@ -7,9 +7,153 @@
 
 namespace Drupal\Core\Entity\Display;
 
+use Drupal\Core\Entity\ContentEntityInterface;
+
 /**
  * Provides a common interface for entity form displays.
  */
 interface EntityFormDisplayInterface extends EntityDisplayInterface {
 
+  /**
+   * Adds field widgets to an entity form.
+   *
+   * The form elements for the entity's fields are added by reference as direct
+   * children in the $form parameter. This parameter can be a full form
+   * structure (most common case for entity edit forms), or a sub-element of a
+   * larger form.
+   *
+   * By default, submitted field values appear at the top-level of
+   * $form_state['values']. A different location within $form_state['values']
+   * can be specified by setting the '#parents' property on the incoming $form
+   * parameter. Because of name clashes, two instances of the same field cannot
+   * appear within the same $form element, or within the same '#parents' space.
+   *
+   * Sample resulting structure in $form:
+   * @code
+   *   '#parents' => The location of field values in $form_state['values'],
+   *   '#entity_type' => The name of the entity type,
+   *   '#bundle' => The name of the bundle,
+   *   // One sub-array per field appearing in the entity, keyed by field name.
+   *   // The structure of the array differs slightly depending on whether the
+   *   // widget is 'single-value' (provides the input for one field value,
+   *   // most common case), and will therefore be repeated as many times as
+   *   // needed, or 'multiple-values' (one single widget allows the input of
+   *   // several values, e.g checkboxes, select box...).
+   *   'field_foo' => array(
+   *     '#access' => TRUE if the current user has 'edit' grants for the field,
+   *       FALSE if not.
+   *     'widget' => array(
+   *       '#field_name' => The name of the field,
+   *       '#language' => $langcode,
+   *       '#field_parents' => The 'parents' space for the field in the form,
+   *          equal to the #parents property of the $form parameter received by
+   *          this method,
+   *       '#required' => Whether or not the field is required,
+   *       '#title' => The label of the field instance,
+   *       '#description' => The description text for the field instance,
+   *
+   *       // Only for 'single' widgets:
+   *       '#theme' => 'field_multiple_value_form',
+   *       '#cardinality' => The field cardinality,
+   *       '#cardinality_multiple => TRUE if the field can contain multiple
+   *         items, FALSE otherwise.
+   *       // One sub-array per copy of the widget, keyed by delta.
+   *       0 => array(
+   *         '#entity_type' => The name of the entity type,
+   *         '#bundle' => The name of the bundle,
+   *         '#field_name' => The name of the field,
+   *         '#field_parents' => The 'parents' space for the field in the form,
+   *            equal to the #parents property of the $form parameter,
+   *         '#title' => The title to be displayed by the widget,
+   *         '#default_value' => The field value for delta 0,
+   *         '#required' => Whether the widget should be marked required,
+   *         '#delta' => 0,
+   *         // The remaining elements in the sub-array depend on the widget.
+   *         '#type' => The type of the widget,
+   *         ...
+   *       ),
+   *       1 => array(
+   *         ...
+   *       ),
+   *
+   *       // Only for multiple widgets:
+   *       '#entity_type' => The name of the entity type,
+   *       '#bundle' => $instance['bundle'],
+   *       // The remaining elements in the sub-array depend on the widget.
+   *       '#type' => The type of the widget,
+   *       ...
+   *     ),
+   *     ...
+   *   ),
+   * )
+   * @endcode
+   *
+   * Additionally, some processing data is placed in $form_state, and can be
+   * accessed by field_form_get_state() and field_form_set_state().
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity.
+   * @param array $form
+   *   The form structure to fill in. This can be a full form structure, or a
+   *   sub-element of a larger form. The #parents property can be set to
+   *   control the location of submitted field values within
+   *   $form_state['values']. If not specified, $form['#parents'] is set to an
+   *   empty array, which results in field values located at the top-level of
+   *   $form_state['values'].
+   * @param array $form_state
+   *   The form state.
+   */
+  public function buildForm(ContentEntityInterface $entity, array &$form, array &$form_state);
+
+  /**
+   * Validates submitted widget values and sets the corresponding form errors.
+   *
+   * There are two levels of validation for fields in forms: widget validation
+   * and field validation.
+   * - Widget validation steps are specific to a given widget's own form
+   *   structure and UI metaphors. They are executed during normal form
+   *   validation, usually through Form API's #element_validate property.
+   *   Errors reported at this level are typically those that prevent the
+   *   extraction of proper field values from the submitted form input.
+   * - If no form / widget errors were reported for the field, field validation
+   *   steps are performed according to the "constraints" specified by the
+   *   field definition. Those are independent of the specific widget being
+   *   used in a given form, and are also performed on REST entity submissions.
+   *
+   * This function performs field validation in the context of a form submission.
+   * It reports field constraint violations as form errors on the correct form
+   * elements.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity.
+   * @param array $form
+   *   The form structure where field elements are attached to. This might be a
+   *   full form structure, or a sub-element of a larger form.
+   * @param array $form_state
+   *   The form state.
+   */
+  public function validateFormValues(ContentEntityInterface $entity, array &$form, array &$form_state);
+
+  /**
+   * Extracts field values from the submitted widget values into the entity.
+   *
+   * This accounts for drag-and-drop reordering of field values, and filtering
+   * of empty values.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity.
+   * @param array $form
+   *   The form structure where field elements are attached to. This might be a
+   *   full form structure, or a sub-element of a larger form.
+   * @param array $form_state
+   *   The form state.
+   *
+   * @return array
+   *   An array whose keys and values are the keys of the top-level entries in
+   *   $form_state['values'] that have been processed. The remaining entries, if
+   *   any, do not correspond to widgets and should be extracted manually by
+   *   the caller if needed.
+   */
+  public function extractFormValues(ContentEntityInterface $entity, array &$form, array &$form_state);
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityFormController.php b/core/lib/Drupal/Core/Entity/EntityFormController.php
index 58d9035..28ace6b 100644
--- a/core/lib/Drupal/Core/Entity/EntityFormController.php
+++ b/core/lib/Drupal/Core/Entity/EntityFormController.php
@@ -7,10 +7,8 @@
 
 namespace Drupal\Core\Entity;
 
-use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Extension\ModuleHandlerInterface;
-use Drupal\entity\Entity\EntityFormDisplay;
 
 /**
  * Base class for entity form controllers.
@@ -122,9 +120,6 @@ protected function init(array &$form_state) {
     // Prepare the entity to be presented in the entity form.
     $this->prepareEntity();
 
-    $form_display = EntityFormDisplay::collectRenderDisplay($this->entity, $this->getOperation());
-    $this->setFormDisplay($form_display, $form_state);
-
     // Invoke the prepare form hooks.
     $this->prepareInvokeAll('entity_prepare_form', $form_state);
     $this->prepareInvokeAll($this->entity->getEntityTypeId() . '_prepare_form', $form_state);
@@ -137,13 +132,8 @@ protected function init(array &$form_state) {
    */
   public function form(array $form, array &$form_state) {
     $entity = $this->entity;
-    // @todo Exploit the Field API to generate the default widgets for the
-    // entity properties.
-    if ($entity->getEntityType()->isFieldable()) {
-      field_attach_form($entity, $form, $form_state, $this->getFormLangcode($form_state));
-    }
 
-    // Add a process callback so we can assign weights and hide extra fields.
+    // Add a process callback.
     $form['#process'][] = array($this, 'processForm');
 
     if (!isset($form['langcode'])) {
@@ -168,25 +158,6 @@ public function processForm($element, $form_state, $form) {
     // to the entity object, hence we must restore it.
     $this->entity = $form_state['controller']->getEntity();
 
-    // Assign the weights configured in the form display.
-    foreach ($this->getFormDisplay($form_state)->getComponents() as $name => $options) {
-      if (isset($element[$name])) {
-        $element[$name]['#weight'] = $options['weight'];
-      }
-    }
-
-    // Hide or assign weights for extra fields.
-    $extra_fields = field_info_extra_fields($this->entity->getEntityTypeId(), $this->entity->bundle(), 'form');
-    foreach ($extra_fields as $extra_field => $info) {
-      $component = $this->getFormDisplay($form_state)->getComponent($extra_field);
-      if (!$component) {
-        $element[$extra_field]['#access'] = FALSE;
-      }
-      else {
-        $element[$extra_field]['#weight'] = $component['weight'];
-      }
-    }
-
     return $element;
   }
 
@@ -359,7 +330,7 @@ public function buildEntity(array $form, array &$form_state) {
     // controller of the current request.
     $form_state['controller'] = $this;
 
-    $this->copyFormValuesToEntity($entity, $form_state);
+    $this->copyFormValuesToEntity($entity, $form, $form_state);
 
     // Invoke all specified builders for copying form values to entity
     // properties.
@@ -380,10 +351,12 @@ public function buildEntity(array $form, array &$form_state) {
    *
    * @param \Drupal\Core\Entity\EntityInterface $entity
    *   The entity the current form should operate upon.
+   * @param array $form
+   *   A nested array of form elements comprising the form.
    * @param array $form_state
    *   An associative array containing the current state of the form.
    */
-  protected function copyFormValuesToEntity(EntityInterface $entity, array $form_state) {
+  protected function copyFormValuesToEntity(EntityInterface $entity, array $form, array &$form_state) {
     // @todo: This relies on a method that only exists for config and content
     //   entities, in a different way. Consider moving this logic to a config
     //   entity specific implementation.
@@ -427,7 +400,7 @@ protected function prepareInvokeAll($hook, array &$form_state) {
       if (function_exists($function)) {
         // Ensure we pass an updated translation object and form display at
         // each invocation, since they depend on form state which is alterable.
-        $args = array($this->entity, $this->getFormDisplay($form_state), $this->operation, &$form_state);
+        $args = array($this->entity, $this->operation, &$form_state);
         call_user_func_array($function, $args);
       }
     }
@@ -436,21 +409,6 @@ protected function prepareInvokeAll($hook, array &$form_state) {
   /**
    * {@inheritdoc}
    */
-  public function getFormDisplay(array $form_state) {
-    return isset($form_state['form_display']) ? $form_state['form_display'] : NULL;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setFormDisplay(EntityFormDisplayInterface $form_display, array &$form_state) {
-    $form_state['form_display'] = $form_display;
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function getOperation() {
     return $this->operation;
   }
diff --git a/core/lib/Drupal/Core/Entity/EntityFormControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityFormControllerInterface.php
index e63679b..5e0881d 100644
--- a/core/lib/Drupal/Core/Entity/EntityFormControllerInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityFormControllerInterface.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\Core\Entity;
 
-use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Form\BaseFormIdInterface;
 use Drupal\Core\StringTranslation\TranslationInterface;
@@ -58,30 +57,6 @@ public function setOperation($operation);
   public function getOperation();
 
   /**
-   * Returns the form display.
-   *
-   * @param array $form_state
-   *   An associative array containing the current state of the form.
-   *
-   * @return \Drupal\Core\Entity\Display\EntityFormDisplayInterface.
-   *   The current form display.
-   */
-  public function getFormDisplay(array $form_state);
-
-  /**
-   * Sets the form display.
-   *
-   * Sets the form display which will be used for populating form element
-   * defaults.
-   *
-   * @param \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display
-   *   The form display that the current form operates with.
-   * @param array $form_state
-   *   An associative array containing the current state of the form.
-   */
-  public function setFormDisplay(EntityFormDisplayInterface $form_display, array &$form_state);
-
-  /**
    * Returns the form entity.
    *
    * The form entity which has been used for populating form element defaults.
diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/FieldItemList.php
index a3e64b0..39be1ee 100644
--- a/core/lib/Drupal/Core/Field/FieldItemList.php
+++ b/core/lib/Drupal/Core/Field/FieldItemList.php
@@ -331,15 +331,9 @@ public function defaultValuesFormValidate(array $element, array &$form, array &$
     $widget->extractFormValues($this, $element, $form_state);
     $violations = $this->validate();
 
+    // Assign reported errors to the correct form element.
     if (count($violations)) {
-      // Store reported errors in $form_state.
-      $field_name = $this->getFieldDefinition()->getName();
-      $field_state = field_form_get_state($element['#parents'], $field_name, $form_state);
-      $field_state['constraint_violations'] = $violations;
-      field_form_set_state($element['#parents'], $field_name, $form_state, $field_state);
-
-      // Assign reported errors to the correct form element.
-      $widget->flagErrors($this, $element, $form_state);
+      $widget->flagErrors($this, $violations, $element, $form_state);
     }
   }
 
diff --git a/core/lib/Drupal/Core/Field/WidgetBase.php b/core/lib/Drupal/Core/Field/WidgetBase.php
index 6432fb1..33ea5d8 100644
--- a/core/lib/Drupal/Core/Field/WidgetBase.php
+++ b/core/lib/Drupal/Core/Field/WidgetBase.php
@@ -11,6 +11,7 @@
 use Drupal\Component\Utility\SortArray;
 use Drupal\Component\Utility\String;
 use Symfony\Component\Validator\ConstraintViolationInterface;
+use Symfony\Component\Validator\ConstraintViolationListInterface;
 
 /**
  * Base class for 'Field widget' plugin implementations.
@@ -61,7 +62,6 @@ public function form(FieldItemListInterface $items, array &$form, array &$form_s
       $field_state = array(
         'items_count' => count($items),
         'array_parents' => array(),
-        'constraint_violations' => array(),
       );
       field_form_set_state($parents, $field_name, $form_state, $field_state);
     }
@@ -355,12 +355,12 @@ public function extractFormValues(FieldItemListInterface $items, array $form, ar
   /**
    * {@inheritdoc}
    */
-  public function flagErrors(FieldItemListInterface $items, array $form, array &$form_state) {
+  public function flagErrors(FieldItemListInterface $items, ConstraintViolationListInterface $violations, array $form, array &$form_state) {
     $field_name = $this->fieldDefinition->getName();
 
     $field_state = field_form_get_state($form['#parents'], $field_name, $form_state);
 
-    if (!empty($field_state['constraint_violations'])) {
+    if ($violations->count()) {
       $form_builder = \Drupal::formBuilder();
 
       // Locate the correct element in the the form.
@@ -384,7 +384,7 @@ public function flagErrors(FieldItemListInterface $items, array $form, array &$f
         $handles_multiple = $this->handlesMultipleValues();
 
         $violations_by_delta = array();
-        foreach ($field_state['constraint_violations'] as $violation) {
+        foreach ($violations as $violation) {
           // Separate violations by delta.
           $property_path = explode('.', $violation->getPropertyPath());
           $delta = array_shift($property_path);
@@ -396,6 +396,7 @@ public function flagErrors(FieldItemListInterface $items, array $form, array &$f
           $violation->arrayPropertyPath = $property_path;
         }
 
+        /** @var \Symfony\Component\Validator\ConstraintViolationInterface[] $delta_violations */
         foreach ($violations_by_delta as $delta => $delta_violations) {
           // Pass violations to the main element:
           // - if this is a multiple-value widget,
@@ -416,9 +417,6 @@ public function flagErrors(FieldItemListInterface $items, array $form, array &$f
             }
           }
         }
-        // Reinitialize the errors list for the next submit.
-        $field_state['constraint_violations'] = array();
-        field_form_set_state($form['#parents'], $field_name, $form_state, $field_state);
       }
     }
   }
diff --git a/core/lib/Drupal/Core/Field/WidgetBaseInterface.php b/core/lib/Drupal/Core/Field/WidgetBaseInterface.php
index 8c63e23..3af0549 100644
--- a/core/lib/Drupal/Core/Field/WidgetBaseInterface.php
+++ b/core/lib/Drupal/Core/Field/WidgetBaseInterface.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\Field;
 
+use Symfony\Component\Validator\ConstraintViolationListInterface;
+
 /**
  * Base interface definition for "Field widget" plugins.
  *
@@ -59,12 +61,14 @@ public function extractFormValues(FieldItemListInterface $items, array $form, ar
    *
    * @param \Drupal\Core\Field\FieldItemListInterface $items
    *   The field values.
+   * @param \Symfony\Component\Validator\ConstraintViolationListInterface|\Symfony\Component\Validator\ConstraintViolationInterface[] $violations
+   *   The constraint violations that were detected.
    * @param array $form
    *   The form structure where field elements are attached to. This might be a
    *   full form structure, or a sub-element of a larger form.
    * @param array $form_state
    *   The form state.
    */
-  public function flagErrors(FieldItemListInterface $items, array $form, array &$form_state);
+  public function flagErrors(FieldItemListInterface $items, ConstraintViolationListInterface $violations, array $form, array &$form_state);
 
 }
diff --git a/core/modules/book/book.module b/core/modules/book/book.module
index 9443a2d..992edf8 100644
--- a/core/modules/book/book.module
+++ b/core/modules/book/book.module
@@ -499,7 +499,7 @@ function book_node_predelete(EntityInterface $node) {
 /**
  * Implements hook_node_prepare_form().
  */
-function book_node_prepare_form(NodeInterface $node, $form_display, $operation, array &$form_state) {
+function book_node_prepare_form(NodeInterface $node, $operation, array &$form_state) {
   // Get BookManager service
   $book_manager = \Drupal::service('book.manager');
 
diff --git a/core/modules/datetime/datetime.module b/core/modules/datetime/datetime.module
index 27b52e4..3a016b5 100644
--- a/core/modules/datetime/datetime.module
+++ b/core/modules/datetime/datetime.module
@@ -1002,7 +1002,7 @@ function datetime_form_node_form_alter(&$form, &$form_state, $form_id) {
 /**
  * Implements hook_node_prepare_form().
  */
-function datetime_node_prepare_form(NodeInterface $node, $form_display, $operation, array &$form_state) {
+function datetime_node_prepare_form(NodeInterface $node, $operation, array &$form_state) {
   // Prepare the 'Authored on' date to use datetime.
   $node->date = DrupalDateTime::createFromTimestamp($node->getCreatedTime());
 }
diff --git a/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php b/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php
index b4f4111..c084fd9 100644
--- a/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php
+++ b/core/modules/edit/lib/Drupal/edit/Form/EditFieldForm.php
@@ -88,7 +88,7 @@ public function buildForm(array $form, array &$form_state, EntityInterface $enti
     }
 
     // Add the field form.
-    field_attach_form($form_state['entity'], $form, $form_state, $form_state['langcode'], array('field_name' =>  $form_state['field_name']));
+    $form_state['form_display']->buildForm($entity, $form, $form_state);
 
     // Add a dummy changed timestamp field to attach form errors to.
     if ($entity instanceof EntityChangedInterface) {
@@ -128,9 +128,15 @@ protected function init(array &$form_state, EntityInterface $entity, $field_name
     $form_state['entity'] = $entity;
     $form_state['field_name'] = $field_name;
 
-    // @todo Allow the usage of different form modes by exposing a hook and the
-    //   UI for them.
-    $form_state['form_display'] = EntityFormDisplay::collectRenderDisplay($entity, 'default');
+    // Fetch the display used by the form. It is the display for the 'default'
+    // form mode, with only the current field visible.
+    $display = EntityFormDisplay::collectRenderDisplay($entity, 'default');
+    foreach ($display->getComponents() as $name => $optipns) {
+      if ($name != $field_name) {
+        $display->removeComponent($name);
+      }
+    }
+    $form_state['form_display'] = $display;
   }
 
   /**
@@ -138,7 +144,8 @@ protected function init(array &$form_state, EntityInterface $entity, $field_name
    */
   public function validateForm(array &$form, array &$form_state) {
     $entity = $this->buildEntity($form, $form_state);
-    field_attach_form_validate($entity, $form, $form_state, array('field_name' =>  $form_state['field_name']));
+
+    $form_state['form_display']->validateFormValues($entity, $form, $form_state);
 
     // Do validation on the changed field as well and assign the error to the
     // dummy form element we added for this. We don't know the name of this
@@ -174,7 +181,7 @@ protected function buildEntity(array $form, array &$form_state) {
     $entity = clone $form_state['entity'];
     $field_name = $form_state['field_name'];
 
-    field_attach_extract_form_values($entity, $form, $form_state, array('field_name' => $field_name));
+    $form_state['form_display']->extractFormValues($entity, $form, $form_state);
 
     // @todo Refine automated log messages and abstract them to all entity
     //   types: http://drupal.org/node/1678002.
diff --git a/core/modules/entity/lib/Drupal/entity/Entity/EntityFormDisplay.php b/core/modules/entity/lib/Drupal/entity/Entity/EntityFormDisplay.php
index 9e3a6a8..29fd6e0 100644
--- a/core/modules/entity/lib/Drupal/entity/Entity/EntityFormDisplay.php
+++ b/core/modules/entity/lib/Drupal/entity/Entity/EntityFormDisplay.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\entity\Entity;
 
+use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
 use Drupal\entity\EntityDisplayBase;
 
@@ -49,7 +50,7 @@ class EntityFormDisplay extends EntityDisplayBase implements EntityFormDisplayIn
    * party code to alter the display options held in the display before they are
    * used to generate render arrays.
    *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
    *   The entity for which the form is being built.
    * @param string $form_mode
    *   The form mode.
@@ -60,7 +61,7 @@ class EntityFormDisplay extends EntityDisplayBase implements EntityFormDisplayIn
    * @see entity_get_form_display()
    * @see hook_entity_form_display_alter()
    */
-  public static function collectRenderDisplay($entity, $form_mode) {
+  public static function collectRenderDisplay(ContentEntityInterface $entity, $form_mode) {
     $entity_type = $entity->getEntityTypeId();
     $bundle = $entity->bundle();
 
@@ -147,6 +148,83 @@ public function getRenderer($field_name) {
   /**
    * {@inheritdoc}
    */
+  public function buildForm(ContentEntityInterface $entity, array &$form, array &$form_state) {
+    // Set #parents to 'top-level' by default.
+    $form += array('#parents' => array());
+
+    // Let each widget generate the form elements.
+    foreach ($entity as $name => $items) {
+      if ($widget = $this->getRenderer($name)) {
+        $items->filterEmptyItems();
+        $form[$name] = $widget->form($items, $form, $form_state);
+
+        // Assign the correct weight. This duplicates the reordering done in
+        // processForm(), but is needed for other forms calling this method
+        // directly.
+        $form[$name]['#weight'] = $this->getComponent($name)['weight'];
+      }
+    }
+
+    // Add a process callback so we can assign weights and hide extra fields.
+    $form['#process'][] = array($this, 'processForm');
+  }
+
+  /**
+   * Process callback: assigns weights and hides extra fields.
+   *
+   * @see \Drupal\entity\Entity\EntityFormDisplay::buildForm()
+   */
+  public function processForm($element, $form_state, $form) {
+    // Assign the weights configured in the form display.
+    foreach ($this->getComponents() as $name => $options) {
+      if (isset($element[$name])) {
+        $element[$name]['#weight'] = $options['weight'];
+      }
+    }
+
+    // Hide extra fields.
+    $extra_fields = field_info_extra_fields($this->targetEntityType, $this->bundle, 'form');
+    foreach ($extra_fields as $extra_field => $info) {
+      if (!$this->getComponent($extra_field)) {
+        $element[$extra_field]['#access'] = FALSE;
+      }
+    }
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function extractFormValues(ContentEntityInterface $entity, array &$form, array &$form_state) {
+    $extracted = array();
+    foreach ($entity as $name => $items) {
+      if ($widget = $this->getRenderer($name)) {
+        $widget->extractFormValues($items, $form, $form_state);
+        $extracted[$name] = $name;
+      }
+    }
+    return $extracted;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateFormValues(ContentEntityInterface $entity, array &$form, array &$form_state) {
+    foreach ($entity as $field_name => $items) {
+      // Only validate the fields that actually appear in the form, and let the
+      // widget assign the violations to the right form elements.
+      if ($widget = $this->getRenderer($field_name)) {
+        $violations = $items->validate();
+        if (count($violations)) {
+          $widget->flagErrors($items, $violations, $form, $form_state);
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function __sleep() {
     // Only store the definition, not external objects or derived data.
     $keys = array_keys($this->toArray());
diff --git a/core/modules/field/field.api.php b/core/modules/field/field.api.php
index 92e9eb6..4ca7d7e 100644
--- a/core/modules/field/field.api.php
+++ b/core/modules/field/field.api.php
@@ -11,15 +11,16 @@
 /**
  * Exposes "pseudo-field" components on fieldable entities.
  *
- * Field UI's "Manage fields" and "Manage display" pages let users re-order
- * fields, but also non-field components. For nodes, these include the title
- * and other elements exposed by modules through hook_form_alter().
+ * Field UI's "Manage display" and "Manage form display" pages let users
+ * re-order fields rendered through the regular widget/formatter pipeline, but
+ * also other components: entity fields that are rendered through custom code,
+ * or other arbitrary components added through hook_form_alter() or
+ * hook_entity_view().
  *
  * Fieldable entities or modules that want to have their components supported
  * should expose them using this hook. The user-defined settings (weight,
  * visible) are automatically applied on rendered forms and displayed entities
- * in a #pre_render callback added by field_attach_form() and
- * EntityViewBuilder::viewMultiple().
+ * by ContentEntityFormController::form() and EntityViewBuilder::viewMultiple().
  *
  * @see hook_field_extra_fields_alter()
  *
@@ -162,8 +163,8 @@ function hook_field_info_alter(&$info) {
  *
  * Widgets are @link forms_api_reference.html Form API @endlink
  * elements with additional processing capabilities. The methods of the
- * WidgetInterface object are typically called by the Field Attach API during
- * the creation of the field form structure with field_attach_form().
+ * WidgetInterface object are typically called by respective methods in the
+ * \Drupal\entity\Entity\EntityFormDisplay class.
  *
  * @see field
  * @see field_types
@@ -292,78 +293,6 @@ function hook_field_formatter_info_alter(array &$info) {
  */
 
 /**
- * @addtogroup field_attach
- * @{
- */
-
-/**
- * Act on field_attach_form().
- *
- * This hook is invoked after the field module has performed the operation.
- * Implementing modules should alter the $form or $form_state parameters.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity for which an edit form is being built.
- * @param $form
- *   The form structure field elements are attached to. This might be a full
- *   form structure, or a sub-element of a larger form. The $form['#parents']
- *   property can be used to identify the corresponding part of
- *   $form_state['values']. Hook implementations that need to act on the
- *   top-level properties of the global form (like #submit, #validate...) can
- *   add a #process callback to the array received in the $form parameter, and
- *   act on the $complete_form parameter in the process callback.
- * @param $form_state
- *   An associative array containing the current state of the form.
- * @param $langcode
- *   The language the field values are going to be entered in. If no language is
- *   provided the default site language will be used.
- *
- * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
- *   Use the entity system instead, see https://drupal.org/developing/api/entity
- */
-function hook_field_attach_form(\Drupal\Core\Entity\EntityInterface $entity, &$form, &$form_state, $langcode) {
-  // Add a checkbox allowing a given field to be emptied.
-  // See hook_field_attach_extract_form_values() for the corresponding
-  // processing code.
-  $form['empty_field_foo'] = array(
-    '#type' => 'checkbox',
-    '#title' => t("Empty the 'field_foo' field"),
-  );
-}
-
-/**
- * Act on field_attach_extract_form_values().
- *
- * This hook is invoked after the field module has performed the operation.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity for which an edit form is being submitted. The incoming form
- *   values have been extracted as field values of the $entity object.
- * @param $form
- *   The form structure field elements are attached to. This might be a full
- *   form structure, or a sub-part of a larger form. The $form['#parents']
- *   property can be used to identify the corresponding part of
- *   $form_state['values'].
- * @param $form_state
- *   An associative array containing the current state of the form.
- *
- * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
- *   Use the entity system instead, see https://drupal.org/developing/api/entity
- */
-function hook_field_attach_extract_form_values(\Drupal\Core\Entity\EntityInterface $entity, $form, &$form_state) {
-  // Sample case of an 'Empty the field' checkbox added on the form, allowing
-  // a given field to be emptied.
-  $values = NestedArray::getValue($form_state['values'], $form['#parents']);
-  if (!empty($values['empty_field_foo'])) {
-    unset($entity->field_foo);
-  }
-}
-
-/**
- * @} End of "addtogroup field_attach".
- */
-
-/**
  * Returns the maximum weight for the entity components handled by the module.
  *
  * Field API takes care of fields and 'extra_fields'. This hook is intended for
diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc
deleted file mode 100644
index a2f6c7c..0000000
--- a/core/modules/field/field.attach.inc
+++ /dev/null
@@ -1,153 +0,0 @@
-<?php
-
-/**
- * @file
- * Field attach API, allowing entities (nodes, users, ...) to be 'fieldable'.
- */
-
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Field\FieldDefinitionInterface;
-
-/**
- * @defgroup field_attach Field Attach API
- * @{
- * Operates on Field API data attached to Drupal entities.
- *
- * Field Attach API functions load, store, display, generate Field API
- * structures, and perform a variety of other functions for field data attached
- * to individual entities.
- *
- * Field Attach API functions generally take $entity_type and $entity arguments
- * along with additional function-specific arguments. $entity_type is the type
- * of the fieldable entity, such as 'node' or 'user', and $entity is the entity
- * itself.
- *
- * An entity plugin's annotation is how entity types define if and how
- * Field API should operate on their entity objects. Notably, the 'fieldable'
- * property needs to be set to TRUE.
- *
- * The Field Attach API uses the concept of bundles: the set of fields for a
- * given entity is defined on a per-bundle basis. The collection of bundles for
- * an entity type is added to the entity definition with
- * hook_entity_type_alter(). For instance, node_entity_type_alter() exposes
- * each node type as its own bundle. This means that the set of fields of a
- * node is determined by the node type.
- *
- * The Field API reads the bundle name for a given entity from a particular
- * property of the entity object, and hook_entity_type_alter() defines which
- * property to use. For instance, \Drupal\node\Entity\Node specifies:
- * @code
- *   entity_keys = {
- *     "bundle" = "type"
- *   }
- * @endcode
- * This indicates that for a particular node object, the bundle name can be
- * found in $node->type. This property can be omitted if the entity type only
- * exposes a single bundle (all entities of this type have the same collection
- * of fields). This is the case for the 'user' entity type.
- *
- * @link field_language Field language API @endlink provides information about
- * the structure of field objects.
- *
- * See @link field Field API @endlink for information about the other parts of
- * the Field API.
- */
-
-/**
- * Invokes a method on all the fields of a given entity.
- *
- * @param string $method
- *   The name of the method to invoke.
- * @param callable $target_function
- *   A function that receives a FieldDefinitionInterface object and returns the
- *   object on which the method should be invoked.
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The fully formed $entity_type entity.
- * @param mixed $a
- *   (optional) A parameter for the invoked method. Defaults to NULL.
- * @param mixed $b
- *   (optional) A parameter for the invoked method. Defaults to NULL.
- * @param array $options
- *   (optional) An associative array of additional options, with the following
- *   keys:
- *   - field_name: The name of the field whose operation should be invoked. By
- *     default, the operation is invoked on all the fields in the entity's
- *     bundle.
- *
- * @return array
- *   An array of returned values.
- */
-function field_invoke_method($method, $target_function, EntityInterface $entity, &$a = NULL, &$b = NULL, array $options = array()) {
-  $entity_type = $entity->getEntityTypeId();
-
-  // Determine the list of fields to iterate on.
-  $field_definitions = _field_invoke_get_field_definitions($entity_type, $entity->bundle(), $options);
-
-  // Iterate through the fields and collect results.
-  $return = array();
-  foreach ($field_definitions as $field_definition) {
-    // Let the function determine the target object on which the method should be
-    // called.
-    $target = call_user_func($target_function, $field_definition);
-
-    if (method_exists($target, $method)) {
-      $field_name = $field_definition->getName();
-      $items = $entity->get($field_name);
-      $items->filterEmptyItems();
-
-      $result = $target->$method($items, $a, $b);
-      if (isset($result)) {
-        $return[$field_name] = $result;
-      }
-    }
-  }
-
-  return $return;
-}
-
-/**
- * Retrieves a list of field definitions to operate on.
- *
- * Helper for field_invoke_method().
- *
- * @param $entity_type
- *   The entity type.
- * @param $bundle
- *   The bundle name.
- * @param $options
- *   An associative array of options, as provided to field_invoke_method(). Only
- *   the following keys are considered:
- *   - field_name
- *   See field_invoke_method() for details.
- *
- * @return
- *   The array of selected field definitions.
- */
-function _field_invoke_get_field_definitions($entity_type, $bundle, $options) {
-  $definitions = \Drupal::entityManager()->getFieldDefinitions($entity_type, $bundle);
-  if (isset($options['field_name'])) {
-    $definitions = array_intersect_key($definitions, array($options['field_name'] => TRUE));
-  }
-  return $definitions;
-}
-
-/**
- * Defines a 'target function' for field_invoke_method().
- *
- * Used to invoke methods on a field's widget.
- *
- * @param \Drupal\entity\Entity\EntityFormDisplay $form_display
- *   An EntityFormDisplay object.
- *
- * @return callable $target_function
- *   A 'target function' for field_invoke_method().
- */
-function _field_invoke_widget_target($form_display) {
-  return function (FieldDefinitionInterface $field_definition) use ($form_display) {
-    return $form_display->getRenderer($field_definition->getName());
-  };
-}
-
-/**
- * @} End of "defgroup field_attach".
- */
diff --git a/core/modules/field/field.deprecated.inc b/core/modules/field/field.deprecated.inc
index f939d0e..50fa0a6 100644
--- a/core/modules/field/field.deprecated.inc
+++ b/core/modules/field/field.deprecated.inc
@@ -174,218 +174,3 @@ function field_info_instances($entity_type = NULL, $bundle_name = NULL) {
 function field_info_instance($entity_type, $field_name, $bundle_name) {
   return Field::fieldInfo()->getInstance($entity_type, $bundle_name, $field_name);
 }
-
-/**
- * Adds 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
- * children in the $form parameter. This parameter can be a full form structure
- * (most common case for entity edit forms), or a sub-element of a larger form.
- *
- * By default, submitted field values appear at the top-level of
- * $form_state['values']. A different location within $form_state['values'] can
- * be specified by setting the '#parents' property on the incoming $form
- * parameter. Because of name clashes, two instances of the same field cannot
- * appear within the same $form element, or within the same '#parents' space.
- *
- * For each call to field_attach_form(), field values are processed by calling
- * field_attach_extract_form_values() on the same $form element.
- *
- * Sample resulting structure in $form:
- * @code
- *   '#parents' => The location of field values in $form_state['values'],
- *   '#entity_type' => The name of the entity type,
- *   '#bundle' => The name of the bundle,
- *   // One sub-array per field appearing in the entity, keyed by field name.
- *   // The structure of the array differs slightly depending on whether the
- *   // widget is 'single-value' (provides the input for one field value,
- *   // most common case), and will therefore be repeated as many times as
- *   // needed, or 'multiple-values' (one single widget allows the input of
- *   // several values, e.g checkboxes, select box...).
- *   'field_foo' => array(
- *     '#access' => TRUE if the current user has 'edit' grants for the field,
- *       FALSE if not.
- *     'widget' => array(
- *       '#field_name' => The name of the field,
- *       '#language' => $langcode,
- *       '#field_parents' => The 'parents' space for the field in the form,
- *          equal to the #parents property of the $form parameter received by
- *          field_attach_form(),
- *       '#required' => Whether or not the field is required,
- *       '#title' => The label of the field instance,
- *       '#description' => The description text for the field instance,
- *
- *       // Only for 'single' widgets:
- *       '#theme' => 'field_multiple_value_form',
- *       '#cardinality' => The field cardinality,
- *       '#cardinality_multiple => TRUE if the field can contain multiple items,
- *         FALSE otherwise.
- *       // One sub-array per copy of the widget, keyed by delta.
- *       0 => array(
- *         '#entity_type' => The name of the entity type,
- *         '#bundle' => The name of the bundle,
- *         '#field_name' => The name of the field,
- *         '#field_parents' => The 'parents' space for the field in the form,
- *            equal to the #parents property of the $form parameter received by
- *            field_attach_form(),
- *         '#title' => The title to be displayed by the widget,
- *         '#default_value' => The field value for delta 0,
- *         '#required' => Whether the widget should be marked required,
- *         '#delta' => 0,
- *         // The remaining elements in the sub-array depend on the widget.
- *         '#type' => The type of the widget,
- *         ...
- *       ),
- *       1 => array(
- *         ...
- *       ),
- *
- *       // Only for multiple widgets:
- *       '#entity_type' => The name of the entity type,
- *       '#bundle' => $instance['bundle'],
- *       // The remaining elements in the sub-array depend on the widget.
- *       '#type' => The type of the widget,
- *       ...
- *     ),
- *     ...
- *   ),
- * )
- * @endcode
- *
- * Additionally, some processing data is placed in $form_state, and can be
- * accessed by field_form_get_state() and field_form_set_state().
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity for which to load form elements, used to initialize
- *   default form values.
- * @param $form
- *   The form structure to fill in. This can be a full form structure, or a
- *   sub-element of a larger form. The #parents property can be set to control
- *   the location of submitted field values within $form_state['values']. If
- *   not specified, $form['#parents'] is set to an empty array, placing field
- *   values at the top-level of $form_state['values'].
- * @param $form_state
- *   An associative array containing the current state of the form.
- * @param $langcode
- *   The language the field values are going to be entered, if no language
- *   is provided the default site language will be used.
- * @param array $options
- *   An associative array of additional options. See field_invoke_method() for
- *   details.
- *
- * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
- *   Use the entity system instead, see https://drupal.org/developing/api/entity
- *
- * @see field_form_get_state()
- * @see field_form_set_state()
- */
-function field_attach_form(EntityInterface $entity, &$form, &$form_state, $langcode = NULL, array $options = array()) {
-  // Set #parents to 'top-level' by default.
-  $form += array('#parents' => array());
-
-  // Get the entity_form_display object for this form.
-  $form_display = $form_state['form_display'];
-
-  $form += (array) field_invoke_method('form', _field_invoke_widget_target($form_display), $entity, $form, $form_state, $options);
-
-  $form['#entity_type'] = $entity->getEntityTypeId();
-  $form['#bundle'] = $entity->bundle();
-
-  // Let other modules make changes to the form.
-  // Avoid \Drupal::moduleHandler()->invokeAll()
-  // to let parameters be taken by reference.
-  foreach (\Drupal::moduleHandler()->getImplementations('field_attach_form') as $module) {
-    $function = $module . '_field_attach_form';
-    $function($entity, $form, $form_state, $langcode);
-  }
-}
-
-/**
- * Performs field validation against form-submitted field values.
- *
- * There are two levels of validation for fields in forms: widget validation and
- * and field validation.
- * - Widget validation steps are specific to a given widget's own form structure
- *   and UI metaphors. They are executed through FAPI's #element_validate
- *   property during normal form validation.
- * - Field validation steps are common to a given field type, independently of
- *   the specific widget being used in a given form. They are defined in the
- *   field type's implementation of hook_field_validate().
- *
- * This function performs field validation in the context of a form submission.
- * It converts field validation errors into form errors on the correct form
- * elements. Fieldable entity types should call this function during their own
- * form validation function.
- *
- * @param \Drupal\Core\Entity\ContentEntityInterface $entity
- *   The entity being submitted. The actual field values will be read
- *   from $form_state['values'].
- * @param $form
- *   The form structure where field elements are attached to. This might be a
- *   full form structure, or a sub-element of a larger form.
- * @param $form_state
- *   An associative array containing the current state of the form.
- * @param array $options
- *   An associative array of additional options. See field_invoke_method() for
- *   details.
- *
- * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
- *   Use the entity system instead, see https://drupal.org/developing/api/entity
- */
-function field_attach_form_validate(ContentEntityInterface $entity, $form, &$form_state, array $options = array()) {
-  $has_violations = FALSE;
-  foreach ($entity as $field_name => $field) {
-    if (empty($options['field_name']) || $options['field_name'] == $field_name) {
-      $field_violations = $field->validate();
-      if (count($field_violations)) {
-        $has_violations = TRUE;
-
-        // Place violations in $form_state.
-        $field_state = field_form_get_state($form['#parents'], $field_name, $form_state);
-        $field_state['constraint_violations'] = $field_violations;
-        field_form_set_state($form['#parents'], $field_name, $form_state, $field_state);
-      }
-    }
-  }
-
-  if ($has_violations) {
-    // Map errors back to form elements.
-    $form_display = $form_state['form_display'];
-    field_invoke_method('flagErrors', _field_invoke_widget_target($form_display), $entity, $form, $form_state, $options);
-  }
-}
-
-/**
- * Populates an entity object with values from a form submission.
- *
- * Currently, this accounts for drag-and-drop reordering of field values, and
- * filtering of empty values.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity being submitted. The actual field values will be read
- *   from $form_state['values'].
- * @param $form
- *   The form structure where field elements are attached to. This might be a
- *   full form structure, or a sub-element of a larger form.
- * @param $form_state
- *   An associative array containing the current state of the form.
- * @param array $options
- *   An associative array of additional options. See field_invoke_method() for
- *   details.
- *
- * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
- *   Use the entity system instead, see https://drupal.org/developing/api/entity
- */
-function field_attach_extract_form_values(EntityInterface $entity, $form, &$form_state, array $options = array()) {
-  // Extract field values from submitted values.
-  $form_display = $form_state['form_display'];
-  field_invoke_method('extractFormValues', _field_invoke_widget_target($form_display), $entity, $form, $form_state, $options);
-
-  // Let other modules act on submitting the entity.
-  // Avoid \Drupal::moduleHandler()->invokeAll()
-  // to let $form_state be taken by reference.
-  foreach (\Drupal::moduleHandler()->getImplementations('field_attach_extract_form_values') as $module) {
-    $function = $module . 'field_attach_extract_form_values';
-    $function($entity, $form, $form_state);
-  }
-}
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index 0c173c2..8524b79 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -16,7 +16,6 @@
  * every page request.
  */
 require_once __DIR__ . '/field.info.inc';
-require_once __DIR__ . '/field.attach.inc';
 require_once __DIR__ . '/field.form.inc';
 require_once __DIR__ . '/field.purge.inc';
 require_once __DIR__ . '/field.deprecated.inc';
diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php
index 1c0518e..1b817ec 100644
--- a/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/FieldAttachOtherTest.php
@@ -262,22 +262,22 @@ function testFieldAttachCache() {
   }
 
   /**
-   * Test field_attach_form().
+   * Tests \Drupal\Core\Entity\Display\EntityFormDisplayInterface::buildForm().
    *
    * This could be much more thorough, but it does verify that the correct
    * widgets show up.
    */
-  function testFieldAttachForm() {
+  function testEntityFormDisplayBuildForm() {
     $this->createFieldWithInstance('_2');
 
     $entity_type = 'entity_test';
     $entity = entity_create($entity_type, array('id' => 1, 'revision_id' => 1, 'type' => $this->instance->bundle));
 
-    // When generating form for all fields.
+    // Test generating widgets for all fields.
+    $display = entity_get_form_display($entity_type, $this->instance->bundle, 'default');
     $form = array();
     $form_state = form_state_defaults();
-    $form_state['form_display'] = entity_get_form_display($entity_type, $this->instance->bundle, 'default');
-    field_attach_form($entity, $form, $form_state);
+    $display->buildForm($entity, $form, $form_state);
 
     $this->assertEqual($form[$this->field_name]['widget']['#title'], $this->instance->getLabel(), "First field's form title is {$this->instance->getLabel()}");
     $this->assertEqual($form[$this->field_name_2]['widget']['#title'], $this->instance_2->getLabel(), "Second field's form title is {$this->instance_2->getLabel()}");
@@ -290,12 +290,16 @@ function testFieldAttachForm() {
       $this->assertEqual($form[$this->field_name_2]['widget'][$delta]['value']['#type'], 'textfield', "Second field's form delta $delta widget is textfield");
     }
 
-    // When generating form for a single field (the second field).
-    $options = array('field_name' => $this->field_name_2);
+    // Test generating widgets for all fields.
+    $display = entity_get_form_display($entity_type, $this->instance->bundle, 'default');
+    foreach ($display->getComponents() as $name => $options) {
+      if ($name != $this->field_name_2) {
+        $display->removeComponent($name);
+      }
+    }
     $form = array();
     $form_state = form_state_defaults();
-    $form_state['form_display'] = entity_get_form_display($entity_type, $this->instance->bundle, 'default');
-    field_attach_form($entity, $form, $form_state, NULL, $options);
+    $display->buildForm($entity, $form, $form_state);
 
     $this->assertFalse(isset($form[$this->field_name]), 'The first field does not exist in the form');
     $this->assertEqual($form[$this->field_name_2]['widget']['#title'], $this->instance_2->getLabel(), "Second field's form title is {$this->instance_2->getLabel()}");
@@ -306,19 +310,19 @@ function testFieldAttachForm() {
   }
 
   /**
-   * Test field_attach_extract_form_values().
+   * Tests \Drupal\Core\Entity\Display\EntityFormDisplayInterface::extractFormValues().
    */
-  function testFieldAttachExtractFormValues() {
+  function testEntityFormDisplayExtractFormValues() {
     $this->createFieldWithInstance('_2');
 
     $entity_type = 'entity_test';
     $entity_init = entity_create($entity_type, array('id' => 1, 'revision_id' => 1, 'type' => $this->instance->bundle));
 
     // Build the form for all fields.
+    $display = entity_get_form_display($entity_type, $this->instance->bundle, 'default');
     $form = array();
     $form_state = form_state_defaults();
-    $form_state['form_display'] = entity_get_form_display($entity_type, $this->instance->bundle, 'default');
-    field_attach_form($entity_init, $form, $form_state);
+    $display->buildForm($entity_init, $form, $form_state);
 
     // Simulate incoming values.
     // First field.
@@ -356,9 +360,9 @@ function testFieldAttachExtractFormValues() {
     $form_state['values'][$this->field_name] = $values;
     $form_state['values'][$this->field_name_2] = $values_2;
 
-    // Call field_attach_extract_form_values() for all fields.
+    // Extract values for all fields.
     $entity = clone($entity_init);
-    field_attach_extract_form_values($entity, $form, $form_state);
+    $display->extractFormValues($entity, $form, $form_state);
 
     asort($weights);
     asort($weights_2);
@@ -378,16 +382,20 @@ function testFieldAttachExtractFormValues() {
     $this->assertIdentical($entity->{$this->field_name_2}->getValue(), $expected_values_2, 'Submit filters empty values');
 
     // Call field_attach_extract_form_values() for a single field (the second field).
-    $options = array('field_name' => $this->field_name_2);
+    foreach ($display->getComponents() as $name => $options) {
+      if ($name != $this->field_name_2) {
+        $display->removeComponent($name);
+      }
+    }
     $entity = clone($entity_init);
-    field_attach_extract_form_values($entity, $form, $form_state, $options);
+    $display->extractFormValues($entity, $form, $form_state);
     $expected_values_2 = array();
     foreach ($weights_2 as $key => $value) {
       if ($key != 1) {
         $expected_values_2[] = array('value' => $values_2[$key]['value']);
       }
     }
-    $this->assertTrue($entity->{$this->field_name}->isEmpty(), 'The first field does is empty in the entity object');
+    $this->assertTrue($entity->{$this->field_name}->isEmpty(), 'The first field is empty in the entity object');
     $this->assertIdentical($entity->{$this->field_name_2}->getValue(), $expected_values_2, 'Submit filters empty values');
   }
 
diff --git a/core/modules/field/lib/Drupal/field/Tests/FormTest.php b/core/modules/field/lib/Drupal/field/Tests/FormTest.php
index 5de1d0a..3b4a96b 100644
--- a/core/modules/field/lib/Drupal/field/Tests/FormTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/FormTest.php
@@ -531,10 +531,10 @@ function testFieldFormAccess() {
     // apart from #access.
     $entity = entity_create($entity_type, array('id' => 0, 'revision_id' => 0));
 
+    $display = entity_get_form_display($entity_type, $entity_type, 'default');
     $form = array();
     $form_state = form_state_defaults();
-    $form_state['form_display'] = entity_get_form_display($entity_type, $entity_type, 'default');
-    field_attach_form($entity, $form, $form_state);
+    $display->buildForm($entity, $form, $form_state);
 
     $this->assertEqual($form[$field_name_no_access]['widget'][0]['value']['#entity_type'], $entity_type, 'The correct entity type is set in the field structure.');
     $this->assertFalse($form[$field_name_no_access]['#access'], 'Field #access is FALSE for the field without edit access.');
diff --git a/core/modules/field/tests/modules/field_test/field_test.entity.inc b/core/modules/field/tests/modules/field_test/field_test.entity.inc
index 824cbfc..e386bf0 100644
--- a/core/modules/field/tests/modules/field_test/field_test.entity.inc
+++ b/core/modules/field/tests/modules/field_test/field_test.entity.inc
@@ -39,86 +39,3 @@ function field_test_entity_info_translatable($entity_type = NULL, $translatable
   }
   return $stored_value;
 }
-
-/**
- * Form combining two separate entities.
- *
- * @deprecated Use \Drupal\field_test\Form\FieldTestForm::testEntityNestedForm()
- */
-function field_test_entity_nested_form($form, &$form_state, EntityInterface $entity_1, EntityInterface $entity_2) {
-  // First entity.
-  foreach (array('id', 'type') as $key) {
-    $form[$key] = array(
-      '#type' => 'value',
-      '#value' => $entity_1->$key->value,
-    );
-  }
-  $form_state['form_display'] = entity_get_form_display($entity_1->getEntityTypeId(), $entity_1->bundle(), 'default');
-  field_attach_form($entity_1, $form, $form_state);
-
-  // Second entity.
-  $form['entity_2'] = array(
-    '#type' => 'details',
-    '#title' => t('Second entity'),
-    '#open' => TRUE,
-    '#tree' => TRUE,
-    '#parents' => array('entity_2'),
-    '#weight' => 50,
-  );
-  foreach (array('id', 'type') as $key) {
-    $form['entity_2'][$key] = array(
-      '#type' => 'value',
-      '#value' => $entity_2->$key->value,
-    );
-  }
-  $form_state['form_display'] = entity_get_form_display($entity_1->getEntityTypeId(), $entity_1->bundle(), 'default');
-  field_attach_form($entity_2, $form['entity_2'], $form_state);
-
-  $form['save'] = array(
-    '#type' => 'submit',
-    '#value' => t('Save'),
-    '#weight' => 100,
-  );
-
-  return $form;
-}
-
-/**
- * Validate handler for field_test_entity_nested_form().
- */
-function field_test_entity_nested_form_validate($form, &$form_state) {
-  $entity_1 = entity_create('entity_test', array(
-    'id' => $form_state['values']['id'],
-    'type' => $form_state['values']['type'],
-  ));
-  field_attach_extract_form_values($entity_1, $form, $form_state);
-  field_attach_form_validate($entity_1, $form, $form_state);
-
-  $entity_2 = entity_create('entity_test', array(
-    'id' => $form_state['values']['entity_2']['id'],
-    'type' => $form_state['values']['entity_2']['type'],
-  ));
-  field_attach_extract_form_values($entity_2, $form['entity_2'], $form_state);
-  field_attach_form_validate($entity_2, $form['entity_2'], $form_state);
-}
-
-/**
- * Submit handler for field_test_entity_nested_form().
- */
-function field_test_entity_nested_form_submit($form, &$form_state) {
-  $entity_1 = entity_create('entity_test', array(
-    'id' => $form_state['values']['id'],
-    'type' => $form_state['values']['type'],
-  ));
-  field_attach_extract_form_values($entity_1, $form, $form_state);
-  $entity_1->save();
-
-  $entity_2 = entity_create('entity_test', array(
-    'id' => $form_state['values']['entity_2']['id'],
-    'type' => $form_state['values']['entity_2']['type'],
-  ));
-  field_attach_extract_form_values($entity_2, $form['entity_2'], $form_state);
-  $entity_2->save();
-
-  drupal_set_message(t('test_entities @id_1 and @id_2 have been updated.', array('@id_1' => $entity_1->id(), '@id_2' => $entity_2->id())));
-}
diff --git a/core/modules/field/tests/modules/field_test/field_test.routing.yml b/core/modules/field/tests/modules/field_test/field_test.routing.yml
index b7ba00c..cfd60fa 100644
--- a/core/modules/field/tests/modules/field_test/field_test.routing.yml
+++ b/core/modules/field/tests/modules/field_test/field_test.routing.yml
@@ -2,7 +2,7 @@ field_test.entity_nested_form:
   path: '/test-entity/nested/{entity_1}/{entity_2}'
   defaults:
     _title: 'Nested entity form'
-    _content: '\Drupal\field_test\Form\FieldTestForm::testEntityNestedForm'
+    _form: '\Drupal\field_test\Form\NestedEntityTestForm'
   options:
     parameters:
       entity_1:
diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Form/FieldTestForm.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Form/FieldTestForm.php
deleted file mode 100644
index 8c9e913..0000000
--- a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Form/FieldTestForm.php
+++ /dev/null
@@ -1,25 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\field_test\Form\FieldTestForm.
- */
-
-namespace Drupal\field_test\Form;
-
-use Drupal\Core\Entity\EntityInterface;
-
-/**
- * Provides a form for field_test routes.
- */
-class FieldTestForm {
-
-  /**
-   * @todo Remove field_test_entity_nested_form().
-   */
-  public function testEntityNestedForm(EntityInterface $entity_1, EntityInterface $entity_2) {
-    module_load_include('entity.inc', 'field_test');
-    return drupal_get_form('field_test_entity_nested_form', $entity_1, $entity_2);
-  }
-
-}
diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Form/NestedEntityTestForm.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Form/NestedEntityTestForm.php
new file mode 100644
index 0000000..89aa42c
--- /dev/null
+++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Form/NestedEntityTestForm.php
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field_test\Form\NestedEntityTestForm.
+ */
+
+namespace Drupal\field_test\Form;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Form\FormBase;
+use Drupal\entity\Entity\EntityFormDisplay;
+
+/**
+ * Provides a form for field_test routes.
+ */
+class NestedEntityTestForm extends FormBase {
+
+  /**
+   * {@inheritdoc]
+   */
+  public function getFormId() {
+    return 'field_test_entity_nested_form';
+  }
+
+  /**
+   * {@inheritdoc]
+   */
+  public function buildForm(array $form, array &$form_state, EntityInterface $entity_1 = NULL, EntityInterface $entity_2 = NULL) {
+    // First entity.
+    $form_state['entity_1'] = $entity_1;
+    $form_state['form_display_1'] = EntityFormDisplay::collectRenderDisplay($entity_1, 'default');
+    $form_state['form_display_1']->buildForm($entity_1, $form, $form_state);
+
+    // Second entity.
+    $form_state['entity_2'] = $entity_2;
+    $form_state['form_display_2'] = EntityFormDisplay::collectRenderDisplay($entity_2, 'default');
+    $form['entity_2'] = array(
+      '#type' => 'details',
+      '#title' => t('Second entity'),
+      '#tree' => TRUE,
+      '#parents' => array('entity_2'),
+      '#weight' => 50,
+    );
+
+    $form_state['form_display_2']->buildForm($entity_2, $form['entity_2'], $form_state);
+
+    $form['save'] = array(
+      '#type' => 'submit',
+      '#value' => t('Save'),
+      '#weight' => 100,
+    );
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc]
+   */
+  public function validateForm(array &$form, array &$form_state) {
+    $entity_1 = $form_state['entity_1'];
+    /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display_1 */
+    $form_display_1 = $form_state['form_display_1'];
+    $form_display_1->extractFormValues($entity_1, $form, $form_state);
+    $form_display_1->validateFormValues($entity_1, $form, $form_state);
+
+    $entity_2 = $form_state['entity_2'];
+    /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display_2 */
+    $form_display_2 = $form_state['form_display_2'];
+    $form_display_2->extractFormValues($entity_2, $form['entity_2'], $form_state);
+    $form_display_2->validateFormValues($entity_2, $form['entity_2'], $form_state);
+  }
+
+  /**
+   * {@inheritdoc]
+   */
+  public function submitForm(array &$form, array &$form_state) {
+    /** @var \Drupal\Core\Entity\EntityInterface $entity_1 */
+    $entity_1 = $form_state['entity_1'];
+    $entity_1->save();
+
+    /** @var \Drupal\Core\Entity\EntityInterface $entity_2 */
+    $entity_2 = $form_state['entity_2'];
+    $entity_2->save();
+
+    drupal_set_message($this->t('test_entities @id_1 and @id_2 have been updated.', array('@id_1' => $entity_1->id(), '@id_2' => $entity_2->id())));
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/Form/ImageStyleEditForm.php b/core/modules/image/lib/Drupal/image/Form/ImageStyleEditForm.php
index a589feb..41465a2 100644
--- a/core/modules/image/lib/Drupal/image/Form/ImageStyleEditForm.php
+++ b/core/modules/image/lib/Drupal/image/Form/ImageStyleEditForm.php
@@ -252,7 +252,7 @@ protected function updateEffectWeights(array $effects) {
   /**
    * {@inheritdoc}
    */
-  protected function copyFormValuesToEntity(EntityInterface $entity, array $form_state) {
+  protected function copyFormValuesToEntity(EntityInterface $entity, array $form, array &$form_state) {
     foreach ($form_state['values'] as $key => $value) {
       // Do not copy effects here, see self::updateEffectWeights().
       if ($key != 'effects') {
diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module
index fa5d8c5..bf90079 100644
--- a/core/modules/menu/menu.module
+++ b/core/modules/menu/menu.module
@@ -390,7 +390,7 @@ function menu_node_predelete(EntityInterface $node) {
 /**
  * Implements hook_node_prepare_form().
  */
-function menu_node_prepare_form(NodeInterface $node, $form_display, $operation, array &$form_state) {
+function menu_node_prepare_form(NodeInterface $node, $operation, array &$form_state) {
   if (empty($node->menu)) {
     // Prepare the node for the edit form so that $node->menu always exists.
     $node_type_config = \Drupal::config('menu.entity.node.' . $node->getType());
diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php
index 844e1a3..1aa1d41 100644
--- a/core/modules/node/node.api.php
+++ b/core/modules/node/node.api.php
@@ -31,13 +31,12 @@
  * - Entity hooks: Generic hooks for "entity" operations. These are always
  *   invoked on all modules.
  *
- * Here is a list of the node and entity hooks that are invoked, field
- * operations, and other steps that take place during node operations:
+ * Here is a list of the node and entity hooks that are invoked, and other
+ * steps that take place during node operations:
  * - Instantiating a new node:
  *   - hook_node_create() (all)
  *   - hook_entity_create() (all)
  * - Creating a new node (calling $node->save() on a new node):
- *   - field_attach_presave()
  *   - hook_node_presave() (all)
  *   - hook_entity_presave() (all)
  *   - Node and revision records are written to the database
@@ -46,7 +45,6 @@
  *   - hook_node_access_records() (all)
  *   - hook_node_access_records_alter() (all)
  * - Updating an existing node (calling $node->save() on an existing node):
- *   - field_attach_presave()
  *   - hook_node_presave() (all)
  *   - hook_entity_presave() (all)
  *   - Node and revision records are written to the database
@@ -91,7 +89,7 @@
  *   existing node, it will already be loaded; see the Loading section above):
  *   - hook_node_prepare_form() (all)
  *   - hook_entity_prepare_form() (all)
- *   - field_attach_form()
+ *   - @todo hook for EntityFormDisplay::buildForm()
  * - Validating a node during editing form submit (calling
  *   node_form_validate()):
  *   - hook_node_validate() (all)
@@ -598,8 +596,6 @@ function hook_node_access(\Drupal\node\NodeInterface $node, $op, $account, $lang
  *
  * @param \Drupal\node\NodeInterface $node
  *   The node that is about to be shown on the form.
- * @param $form_display
- *   The current form display.
  * @param $operation
  *   The current operation.
  * @param array $form_state
@@ -607,7 +603,7 @@ function hook_node_access(\Drupal\node\NodeInterface $node, $op, $account, $lang
  *
  * @ingroup node_api_hooks
  */
-function hook_node_prepare_form(\Drupal\node\NodeInterface $node, $form_display, $operation, array &$form_state) {
+function hook_node_prepare_form(\Drupal\node\NodeInterface $node, $operation, array &$form_state) {
   if (!isset($node->my_rating)) {
     $node->my_rating = \Drupal::config("my_rating_{$node->bundle()}")->get('enabled');
   }
diff --git a/core/modules/system/entity.api.php b/core/modules/system/entity.api.php
index c6cb1d9..0acf2c3 100644
--- a/core/modules/system/entity.api.php
+++ b/core/modules/system/entity.api.php
@@ -620,8 +620,6 @@ function hook_entity_display_build_alter(&$build, $context) {
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity that is about to be shown on the form.
- * @param $form_display
- *   The current form display.
  * @param $operation
  *   The current operation.
  * @param array $form_state
@@ -629,7 +627,7 @@ function hook_entity_display_build_alter(&$build, $context) {
  *
  * @see \Drupal\Core\Entity\EntityFormController::prepareEntity()
  */
-function hook_entity_prepare_form(\Drupal\Core\Entity\EntityInterface $entity, $form_display, $operation, array &$form_state) {
+function hook_entity_prepare_form(\Drupal\Core\Entity\EntityInterface $entity, $operation, array &$form_state) {
   if ($operation == 'edit') {
     $entity->label->value = 'Altered label';
     $form_state['mymodule']['label_altered'] = TRUE;
diff --git a/core/modules/system/lib/Drupal/system/Tests/Form/ArbitraryRebuildTest.php b/core/modules/system/lib/Drupal/system/Tests/Form/ArbitraryRebuildTest.php
index 97c8130..030a2e1 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Form/ArbitraryRebuildTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Form/ArbitraryRebuildTest.php
@@ -76,7 +76,7 @@ function testUserRegistrationMultipleField() {
       'name' => 'foo',
       'mail' => 'bar@example.com',
     );
-    $this->drupalPostForm('user/register', $edit, t('Add another item'), array('query' => array('field' => TRUE)));
+    $this->drupalPostForm('user/register', $edit, t('Add another item'));
     $this->assertText('Test a multiple valued field', 'Form has been rebuilt.');
     $this->assertFieldByName('name', 'foo', 'Entered user name has been kept.');
     $this->assertFieldByName('mail', 'bar@example.com', 'Entered mail address has been kept.');
diff --git a/core/modules/system/tests/modules/form_test/form_test.module b/core/modules/system/tests/modules/form_test/form_test.module
index de5c2f0..43d5d25 100644
--- a/core/modules/system/tests/modules/form_test/form_test.module
+++ b/core/modules/system/tests/modules/form_test/form_test.module
@@ -1942,13 +1942,6 @@ function form_test_form_user_register_form_alter(&$form, &$form_state) {
     '#value' => t('Rebuild'),
     '#submit' => array('form_test_user_register_form_rebuild'),
   );
-  // If requested, add the test field by attaching the node page form.
-  if (\Drupal::request()->request->has('field')) {
-    $node = entity_create('node', array(
-      'type' => 'page',
-    ));
-    field_attach_form($node, $form, $form_state);
-  }
 }
 
 /**
diff --git a/core/modules/user/lib/Drupal/user/RegisterFormController.php b/core/modules/user/lib/Drupal/user/RegisterFormController.php
index 89c91a0..30e33fe 100644
--- a/core/modules/user/lib/Drupal/user/RegisterFormController.php
+++ b/core/modules/user/lib/Drupal/user/RegisterFormController.php
@@ -50,9 +50,6 @@ public function form(array $form, array &$form_state) {
     // Start with the default user account fields.
     $form = parent::form($form, $form_state, $account);
 
-    // Attach field widgets.
-    field_attach_form($account, $form, $form_state);
-
     if ($admin) {
       // Redirect back to page which initiated the create request; usually
       // admin/people/create.
