diff --git a/core/modules/options/lib/Drupal/options/Tests/OptionsArrayFlattenUnitTest.php b/core/modules/options/lib/Drupal/options/Tests/OptionsArrayFlattenUnitTest.php
new file mode 100644
index 0000000..259c5d5
--- /dev/null
+++ b/core/modules/options/lib/Drupal/options/Tests/OptionsArrayFlattenUnitTest.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\options\Tests\OptionsFieldUITest.
+ */
+
+namespace Drupal\options\Tests;
+
+use Drupal\simpletest\DrupalUnitTestBase;
+
+/**
+ * Tests the array flatten utility function.
+ */
+class OptionsArrayFlattenUnitTest extends DrupalUnitTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Options flatten array',
+        'description' => 'Test multi-dimensional array is flattened.',
+        'group' => 'Field types',
+      );
+  }
+
+  public function testArrayFlatten() {
+    $p = array('v1' => 'l1', 'g1' => array('v2' => 'l2', 'v3' => 'l3'));
+    $sub = options_array_flatten($p);
+    $this->assertEqual(array('v1' => 'l1', 'v2' => 'l2', 'v3' => 'l3'), $sub);
+
+    $p = array (
+        'parent1' => array(3 => 'child1', 4 => 'child2', 5 => 'child3'),
+        'parent2' => array(8 => 'child1', 9 => 'child2')
+    );
+    $sub = options_array_flatten($p);
+    $this->assertEqual(array(
+        3 => 'child1', 4 => 'child2', 5 => 'child3',
+        8 => 'child1', 9 => 'child2'), $sub
+    );
+  }
+}
diff --git a/core/modules/options/options.module b/core/modules/options/options.module
index 1d328f6..9e224f8 100644
--- a/core/modules/options/options.module
+++ b/core/modules/options/options.module
@@ -133,3 +133,74 @@ function _options_values_in_use($entity_type, $field_name, $values) {
 
   return FALSE;
 }
+
+/**
+ * Implements hook_field_validate().
+ *
+ * Possible error codes:
+ * - 'list_illegal_value': The value is not part of the list of allowed values.
+ */
+function options_field_validate(EntityInterface $entity = NULL, $field, $instance, $langcode, $items, &$errors) {
+  // When configuring a field instance, the default value is validated without
+  // an entity, but options_allowed_values() and the callback it invokes
+  // require an entity, because the result can depend on entity type, bundle,
+  // and other entity data.
+  if (!isset($entity)) {
+    $ids = (object) array('entity_type' => $instance->entity_type, 'bundle' => $instance->bundle, 'entity_id' => NULL);
+    $entity = _field_create_entity_from_ids($ids);
+  }
+
+  // Flatten the array before validating to account for optgroups.
+  $allowed_values = options_array_flatten(options_allowed_values($field, $instance, $entity));
+  foreach ($items as $delta => $item) {
+    if (!empty($item['value'])) {
+      if (!empty($allowed_values) && !isset($allowed_values[$item['value']])) {
+        $errors[$field['field_name']][$langcode][$delta][] = array(
+          'error' => 'list_illegal_value',
+          'message' => t('%name: illegal value.', array('%name' => $instance['label'])),
+        );
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_field_is_empty().
+ */
+function options_field_is_empty($item, $field_type) {
+  if (empty($item['value']) && (string) $item['value'] !== '0') {
+    return TRUE;
+  }
+  return FALSE;
+}
+
+/**
+ * Implements hook_options_list().
+ */
+function options_options_list(FieldDefinitionInterface $field_definition, EntityInterface $entity) {
+  return options_allowed_values($field_definition, $entity);
+}
+
+/**
+ * Flattens an array of allowed values.
+ *
+ * @param array $array
+ *   A single or multidimensional array
+ *
+ * @return array
+ *   A flattened array
+ */
+function options_array_flatten($array) {
+  $result = array();
+  if (is_array($array)) {
+    foreach ($array as $key => $value) {
+      if (is_array($value)) {
+        $result += options_array_flatten($value);
+      }
+      else {
+        $result[$key] = $value;
+      }
+    }
+  }
+  return $result;
+}
