diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/MultipleWidgetBase.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/MultipleWidgetBase.php
new file mode 100644
index 0000000..2410099
--- /dev/null
+++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/MultipleWidgetBase.php
@@ -0,0 +1,212 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\field\Plugin\Type\Widget\MultipleWidgetBase.
+ */
+
+namespace Drupal\field\Plugin\Type\Widget;
+
+use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\field\Plugin\PluginSettingsBase;
+use Drupal\field\FieldInstance;
+
+/**
+ * Base class for multiple value 'Field widget' plugin implementations.
+ */
+abstract class MultipleWidgetBase extends WidgetBase implements WidgetInterface {
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Widget\WidgetInterface::form().
+   */
+  public function form(EntityInterface $entity, $langcode, array $items, array &$form, array &$form_state, $get_delta = NULL) {
+    $entity_type = $entity->entityType();
+    $field = $this->field;
+    $instance = $this->instance;
+    $field_name = $field['field_name'];
+
+    $parents = $form['#parents'];
+
+    $addition = array(
+      $field_name => array(),
+    );
+
+    // Populate widgets with default values when creating a new entity.
+    if (empty($items) && ($entity->isNew())) {
+      $items = field_get_default_value($entity_type, $entity, $field, $instance, $langcode);
+    }
+
+    // Store field information in $form_state.
+    if (!field_form_get_state($parents, $field_name, $langcode, $form_state)) {
+      $field_state = array(
+          'field' => $field,
+          'instance' => $instance,
+          'items_count' => count($items),
+          'array_parents' => array(),
+          'errors' => array(),
+      );
+      field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state);
+    }
+
+    // Collect widget elements.
+    $elements = array();
+
+    $delta = isset($get_delta) ? $get_delta : 0;
+    $element = array(
+      '#title' => check_plain($instance['label']),
+      '#description' => field_filter_xss(token_replace($instance['description'])),
+    );
+    $element = $this->formSingleElement($entity, $items, $delta, $langcode, $element, $form, $form_state);
+
+    if ($element) {
+      if (isset($get_delta)) {
+        // If we are processing a specific delta value for a field where the
+        // field module handles multiples, set the delta in the result.
+        $elements[$delta] = $element;
+      }
+      else {
+        // For fields that handle their own processing, we cannot make
+        // assumptions about how the field is structured, just merge in the
+        // returned element.
+        $elements = $element;
+      }
+    }
+
+    // Also aid in theming of field widgets by rendering a classified
+    // container.
+    $addition[$field_name] = array(
+      '#type' => 'container',
+      '#attributes' => array(
+        'class' => array(
+          'field-type-' . drupal_html_class($field['type']),
+          'field-name-' . drupal_html_class($field_name),
+          'field-widget-' . drupal_html_class($this->getPluginId()),
+        ),
+      ),
+      '#weight' => $this->weight,
+    );
+
+    // Populate the 'array_parents' information in $form_state['field'] after
+    // the form is built, so that we catch changes in the form structure performed
+    // in alter() hooks.
+    $elements['#after_build'][] = 'field_form_element_after_build';
+    $elements['#field_name'] = $field_name;
+    $elements['#language'] = $langcode;
+    $elements['#field_parents'] = $parents;
+
+    $addition[$field_name] += array(
+      '#tree' => TRUE,
+      // The '#language' key can be used to access the field's form element
+      // when $langcode is unknown.
+      '#language' => $langcode,
+      $langcode => $elements,
+      '#access' => field_access('edit', $field, $entity_type, $entity),
+    );
+
+    return $addition;
+  }
+
+  /**
+   * Generates the form element for a single copy of the widget.
+   */
+  protected function formSingleElement(EntityInterface $entity, array $items, $delta, $langcode, array $element, array &$form, array &$form_state) {
+    $instance = $this->instance;
+    $field = $this->field;
+
+    $element += array(
+      '#entity_type' => $entity->entityType(),
+      '#bundle' => $entity->bundle(),
+      '#entity' => $entity,
+      '#field_name' => $field['field_name'],
+      '#language' => $langcode,
+      '#field_parents' => $form['#parents'],
+      '#columns' => array_keys($field['columns']),
+      // Only the first widget should be required.
+      '#required' => $delta == 0 && $instance['required'],
+      '#delta' => $delta,
+      '#weight' => $delta,
+    );
+
+    $element = $this->formElement($items, $delta, $element, $langcode, $form, $form_state);
+
+    if ($element) {
+      // Allow modules to alter the field widget form element.
+      $context = array(
+        'form' => $form,
+        'field' => $field,
+        'instance' => $instance,
+        'langcode' => $langcode,
+        'items' => $items,
+        'delta' => $delta,
+        'default' => !empty($entity->field_ui_default_value),
+      );
+      drupal_alter(array('field_widget_form', 'field_widget_' . $this->getPluginId() . '_form'), $element, $form_state, $context);
+    }
+
+    return $element;
+  }
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Widget\WidgetInterface::submit().
+   */
+  public function submit(EntityInterface $entity, $langcode, array &$items, array $form, array &$form_state) {
+    $field_name = $this->field['field_name'];
+
+    // Extract the values from $form_state['values'].
+    $path = array_merge($form['#parents'], array($field_name, $langcode));
+    $key_exists = NULL;
+    $values = drupal_array_get_nested_value($form_state['values'], $path, $key_exists);
+
+    if ($key_exists) {
+
+      $items = $this->massageFormValues($values, $form, $form_state);
+
+      foreach ($items as $delta => &$item) {
+        // The tasks below are going to reshuffle deltas. Keep track of the
+        // original deltas for correct reporting of errors in flagErrors().
+        $item['_original_delta'] = $delta;
+      }
+
+      // Remove empty values.
+      $items = _field_filter_items($this->field, $items);
+
+      // Put delta mapping in $form_state, so that flagErrors() can use it.
+      $field_state = field_form_get_state($form['#parents'], $field_name, $langcode, $form_state);
+      foreach ($items as $delta => &$item) {
+        $field_state['original_deltas'][$delta] = $item['_original_delta'];
+        unset($item['_original_delta']);
+      }
+      field_form_set_state($form['#parents'], $field_name, $langcode, $form_state, $field_state);
+    }
+  }
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Widget\WidgetInterface::flagErrors().
+   */
+  public function flagErrors(EntityInterface $entity, $langcode, array $items, array $form, array &$form_state) {
+    $field_name = $this->field['field_name'];
+
+    $field_state = field_form_get_state($form['#parents'], $field_name, $langcode, $form_state);
+
+    if (!empty($field_state['errors'])) {
+      // Locate the correct element in the the form.
+      $element = drupal_array_get_nested_value($form_state['complete_form'], $field_state['array_parents']);
+
+      // Only set errors if the element is accessible.
+      if (!isset($element['#access']) || $element['#access']) {
+        foreach ($field_state['errors'] as $delta => $delta_errors) {
+          // For a multiple-value widget, pass all errors to the main widget.
+          foreach ($delta_errors as $error) {
+            $error_element = $this->errorElement($element, $error, $form, $form_state);
+            form_error($error_element, $error['message']);
+          }
+        }
+        // Reinitialize the errors list for the next submit.
+        $field_state['errors'] = array();
+        field_form_set_state($form['#parents'], $field_name, $langcode, $form_state, $field_state);
+      }
+    }
+  }
+
+}
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php
index 1dbe4bb..5a4ff70 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetBase.php
@@ -102,39 +102,7 @@ public function form(EntityInterface $entity, $langcode, array $items, array &$f
     }
 
     // Collect widget elements.
-    $elements = array();
-
-    // If the widget is handling multiple values (e.g Options), or if we are
-    // displaying an individual element, just get a single form element and make
-    // it the $delta value.
-    $definition = $this->getDefinition();
-    if (isset($get_delta) || $definition['multiple_values']) {
-      $delta = isset($get_delta) ? $get_delta : 0;
-      $element = array(
-        '#title' => check_plain($instance['label']),
-        '#description' => field_filter_xss(token_replace($instance['description'])),
-      );
-      $element = $this->formSingleElement($entity, $items, $delta, $langcode, $element, $form, $form_state);
-
-      if ($element) {
-        if (isset($get_delta)) {
-          // If we are processing a specific delta value for a field where the
-          // field module handles multiples, set the delta in the result.
-          $elements[$delta] = $element;
-        }
-        else {
-          // For fields that handle their own processing, we cannot make
-          // assumptions about how the field is structured, just merge in the
-          // returned element.
-          $elements = $element;
-        }
-      }
-    }
-    // If the widget does not handle multiple values itself, (and we are not
-    // displaying an individual element), process the multiple value form.
-    else {
-      $elements = $this->formMultipleElements($entity, $items, $langcode, $form, $form_state);
-    }
+    $elements = $this->formMultipleElements($entity, $items, $langcode, $form, $form_state);
 
     // Also aid in theming of field widgets by rendering a classified
     // container.
@@ -375,19 +343,10 @@ public function flagErrors(EntityInterface $entity, $langcode, array $items, arr
 
       // Only set errors if the element is accessible.
       if (!isset($element['#access']) || $element['#access']) {
-        $definition = $this->getDefinition();
-        $is_multiple = $definition['multiple_values'];
-
         foreach ($field_state['errors'] as $delta => $delta_errors) {
-          // For a multiple-value widget, pass all errors to the main widget.
           // For single-value widgets, pass errors by delta.
-          if ($is_multiple) {
-            $delta_element = $element;
-          }
-          else {
-            $original_delta = $field_state['original_deltas'][$delta];
-            $delta_element = $element[$original_delta];
-          }
+          $original_delta = $field_state['original_deltas'][$delta];
+          $delta_element = $element[$original_delta];
           foreach ($delta_errors as $error) {
             $error_element = $this->errorElement($delta_element, $error, $form, $form_state);
             form_error($error_element, $error['message']);
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
index 48dee26..9140275 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetLegacyDiscoveryDecorator.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Widget/WidgetLegacyDiscoveryDecorator.php
@@ -36,7 +36,7 @@ public function processDefinition(array &$definition) {
       unset($definition['field types']);
     }
     if (isset($definition['behaviors']['multiple values'])) {
-      $definition['multiple_values'] = $definition['behaviors']['multiple values'];
+      $definition['class'] = '\Drupal\field\Plugin\field\widget\MultipleLegacyWidget';
       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 5722807..07172be 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
@@ -25,7 +25,6 @@ class WidgetPluginManager extends PluginManagerBase {
   protected $defaults = array(
     'field_types' => array(),
     'settings' => array(),
-    'multiple_values' => FALSE,
     'default_value' => TRUE,
   );
 
diff --git a/core/modules/field/lib/Drupal/field/Plugin/field/widget/LegacyWidget.php b/core/modules/field/lib/Drupal/field/Plugin/field/widget/LegacyWidget.php
index 166bd87..fc36734 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/field/widget/LegacyWidget.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/field/widget/LegacyWidget.php
@@ -77,22 +77,19 @@ public function flagErrors(EntityInterface $entity, $langcode, array $items, arr
       // Only set errors if the element is accessible.
       if (!isset($element['#access']) || $element['#access']) {
         $definition = $this->getDefinition();
-        $is_multiple = $definition['multiple_values'];
         $function = $definition['module'] . '_field_widget_error';
         $function_exists = function_exists($function);
 
         foreach ($field_state['errors'] as $delta => $delta_errors) {
           // For multiple single-value widgets, pass errors by delta.
-          // For a multiple-value widget, pass all errors to the main widget.
-          $error_element = $is_multiple ? $element : $element[$delta];
           foreach ($delta_errors as $error) {
             if ($function_exists) {
-              $function($error_element, $error, $form, $form_state);
+              $function($element[$delta], $error, $form, $form_state);
             }
             else {
               // Make sure that errors are reported (even incorrectly flagged) if
               // the widget module fails to implement hook_field_widget_error().
-              form_error($error_element, $error['message']);
+              form_error($element[$delta], $error['message']);
             }
           }
         }
diff --git a/core/modules/field/lib/Drupal/field/Plugin/field/widget/MultipleLegacyWidget.php b/core/modules/field/lib/Drupal/field/Plugin/field/widget/MultipleLegacyWidget.php
new file mode 100644
index 0000000..1aa8f58
--- /dev/null
+++ b/core/modules/field/lib/Drupal/field/Plugin/field/widget/MultipleLegacyWidget.php
@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\field\Plugin\field\widget\MultipleLegacyWidget.
+ */
+
+namespace Drupal\field\Plugin\field\widget;
+
+use Drupal\field\Plugin\Type\Widget\MultipleWidgetBase;
+
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\field\Plugin\Type\Widget\WidgetBase;
+
+/**
+ * Plugin implementation for legacy widgets that handles multiple values (e.g Options).
+ *
+ * This special implementation acts as a temporary BC layer for widgets that
+ * have not been converted to Plugins, and bridges new methods to the old-style
+ * hook_field_widget_*() callbacks.
+ *
+ * This class is not discovered by the annotations reader, but referenced by
+ * the Drupal\field\Plugin\Discovery\LegacyDiscoveryDecorator.
+ *
+ * @todo Remove once all core widgets have been converted.
+ */
+class MultipleLegacyWidget extends MultipleWidgetBase {
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Widget\WidgetInterface::settingsForm().
+   */
+  public function settingsForm(array $form, array &$form_state) {
+    $definition = $this->getDefinition();
+    $function = $definition['module'] . '_field_widget_settings_form';
+    if (function_exists($function)) {
+      return $function($this->field, $this->instance);
+    }
+    return array();
+  }
+
+  /**
+   * Implements Drupal\field\Plugin\Type\Widget\WidgetInterface::formElement().
+   */
+  public function formElement(array $items, $delta, array $element, $langcode, array &$form, array &$form_state) {
+    $definition = $this->getDefinition();
+    $function = $definition['module'] . '_field_widget_form';
+
+    if (function_exists($function)) {
+      // hook_field_widget_form() implementations read widget properties directly
+      // from $instance. Put the actual properties we use here, which might have
+      // been altered by hook_field_widget_property().
+      $instance = clone $this->instance;
+      $instance['widget']['type'] = $this->getPluginId();
+      $instance['widget']['settings'] = $this->getSettings();
+
+      return $function($form, $form_state, $this->field, $instance, $langcode, $items, $delta, $element);
+    }
+    return array();
+  }
+
+  /**
+   * Overrides Drupal\field\Plugin\Type\Widget\WidgetBase::flagErrors().
+   *
+   * In D7, hook_field_widget_error() was supposed to call form_error() itself,
+   * whereas the new errorElement() method simply returns the element to flag.
+   * So we override the flagError() method to be more similar to the previous
+   * code in field_default_form_errors().
+   */
+  public function flagErrors(EntityInterface $entity, $langcode, array $items, array $form, array &$form_state) {
+    $field_name = $this->field['field_name'];
+
+    $field_state = field_form_get_state($form['#parents'], $field_name, $langcode, $form_state);
+
+    if (!empty($field_state['errors'])) {
+      // Locate the correct element in the form.
+      $element = drupal_array_get_nested_value($form_state['complete_form'], $field_state['array_parents']);
+      // Only set errors if the element is accessible.
+      if (!isset($element['#access']) || $element['#access']) {
+        $definition = $this->getDefinition();
+        $function = $definition['module'] . '_field_widget_error';
+        $function_exists = function_exists($function);
+
+        foreach ($field_state['errors'] as $delta => $delta_errors) {
+          // For a multiple-value widget, pass all errors to the main widget.
+          foreach ($delta_errors as $error) {
+            if ($function_exists) {
+              $function($element, $error, $form, $form_state);
+            }
+            else {
+              // Make sure that errors are reported (even incorrectly flagged) if
+              // the widget module fails to implement hook_field_widget_error().
+              form_error($$element, $error['message']);
+            }
+          }
+        }
+        // Reinitialize the errors list for the next submit.
+        $field_state['errors'] = array();
+        field_form_set_state($form['#parents'], $field_name, $langcode, $form_state, $field_state);
+      }
+    }
+  }
+
+}
diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/widget/TestFieldWidgetMultiple.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/widget/TestFieldWidgetMultiple.php
index 91f333a..799011d 100644
--- a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/widget/TestFieldWidgetMultiple.php
+++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/field/widget/TestFieldWidgetMultiple.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\field_test\Plugin\field\widget;
 
+use Drupal\field\Plugin\Type\Widget\MultipleWidgetBase;
+
 use Drupal\Core\Annotation\Plugin;
 use Drupal\Core\Annotation\Translation;
 use Drupal\field\Plugin\Type\Widget\WidgetBase;
@@ -25,10 +27,9 @@
  *   settings = {
  *     "test_widget_setting_multiple" = "dummy test string"
  *   },
- *   multiple_values = TRUE
  * )
  */
-class TestFieldWidgetMultiple extends WidgetBase {
+class TestFieldWidgetMultiple extends MultipleWidgetBase {
 
   /**
    * Implements Drupal\field\Plugin\Type\Widget\WidgetInterface::settingsForm().
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/field/widget/TaxonomyAutocompleteWidget.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/field/widget/TaxonomyAutocompleteWidget.php
index 3f4d510..2d4dbb9 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/field/widget/TaxonomyAutocompleteWidget.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/field/widget/TaxonomyAutocompleteWidget.php
@@ -9,7 +9,7 @@
 
 use Drupal\Core\Annotation\Plugin;
 use Drupal\Core\Annotation\Translation;
-use Drupal\field\Plugin\Type\Widget\WidgetBase;
+use Drupal\field\Plugin\Type\Widget\MultipleWidgetBase;
 
 /**
  * Plugin implementation of the 'taxonomy_autocomplete' widget.
@@ -25,10 +25,9 @@
  *     "size" = "60",
  *     "autocomplete_path" = "taxonomy/autocomplete"
  *   },
- *   multiple_values = TRUE
  * )
  */
-class TaxonomyAutocompleteWidget extends WidgetBase {
+class TaxonomyAutocompleteWidget extends MultipleWidgetBase {
 
   /**
    * Implements Drupal\field\Plugin\Type\Widget\WidgetInterface::formElement().
