diff --git a/core/includes/form.inc b/core/includes/form.inc
index c94bc62..c05f59a 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -2324,41 +2324,66 @@ function form_type_checkboxes_value($element, $input = FALSE) {
  * @param $input
  *   (optional) The incoming input to populate the form element. If FALSE, the
  *   element's default value is returned. Defaults to FALSE.
+ * @param $form_state
+ *   (optional) A keyed array containing the current state of the form.
  *
  * @return
  *   The data that will appear in the $element_state['values'] collection for
  *   this element.
+ *
+ * @see form_type_radio_value()
  */
-function form_type_radios_value(&$element, $input = FALSE) {
+function form_type_radios_value(&$element, $input = FALSE, $form_state = array()) {
+  // Radio buttons can have a submitted value or default value of NULL,
+  // indicating that nothing has been selected. However, if NULL is submitted,
+  // _form_builder_handle_input_element() will replace it with an empty string,
+  // which can cause one of the radio buttons to be incorrectly selected (if an
+  // empty string is one of the #options) or the form to fail validation (if it
+  // is not one of the #options). To prevent this, the #has_garbage_value
+  // property is set, to signify to the form API that its standard handling of
+  // NULL values should not be used.
+  if (!isset($input) || ($input === FALSE && !isset($element['#default_value']))) {
+    $element['#has_garbage_value'] = TRUE;
+  }
+
+  // Besides the above, the form API's standard value handling is correct for
+  // radios in the case where input was submitted.
   if ($input !== FALSE) {
-    // There may not be a submitted value for multiple radio buttons, if none of
-    // the options was checked by default. If there is no submitted input value
-    // for this element (NULL), _form_builder_handle_input_element()
-    // automatically attempts to use the #default_value (if set) or an empty
-    // string (''). However, an empty string would fail validation in
-    // _form_validate(), in case it is not contained in the list of allowed
-    // values in #options.
-    if (!isset($input)) {
-      // Signify a garbage value to disable the #default_value handling and take
-      // over NULL as #value.
-      $element['#has_garbage_value'] = TRUE;
-      // There was a user submission so validation is a must. If this element is
-      // #required, then an appropriate error message will be output. While an
-      // optional #type 'radios' does not necessarily make sense from a user
-      // interaction perspective, there may be use-cases for that and it is not
-      // the job of Form API to artificially limit possibilities.
-      $element['#needs_validation'] = TRUE;
-    }
-    // The value stays the same, but the flags above will ensure it is
-    // processed properly.
     return $input;
   }
-  elseif (isset($element['#default_value'])) {
+  // However, because we let NULL values through, the form API may sometimes
+  // request default value handling for radios even when input was submitted.
+  // Make sure we don't replace the submitted NULL value with the default value
+  // in that case.
+  elseif (isset($element['#default_value']) && empty($form_state['input'])) {
     return $element['#default_value'];
   }
 }
 
 /**
+ * Form value callback: Determines the value for a #type radio form element.
+ *
+ * @param $element
+ *   The form element whose value is being populated.
+ * @param $input
+ *   (optional) The incoming input to populate the form element. If FALSE, the
+ *   element's default value is returned. Defaults to FALSE.
+ * @param $form_state
+ *   (optional) A keyed array containing the current state of the form.
+ *
+ * @return
+ *   The data that will appear in the $element_state['values'] collection for
+ *   this element.
+ *
+ * @see form_type_radios_value()
+ */
+function form_type_radio_value(&$element, $input = FALSE, $form_state = array()) {
+  // A single radio button's value is determined the same way as a set of radio
+  // buttons.
+  return form_type_radios_value($element, $input, $form_state);
+}
+
+/**
  * Determines the value for a tableselect form element.
  *
  * @param $element
@@ -2805,7 +2830,7 @@ function theme_radio($variables) {
   $element['#attributes']['type'] = 'radio';
   element_set_attributes($element, array('id', 'name', '#return_value' => 'value'));
 
-  if (isset($element['#return_value']) && $element['#value'] !== FALSE && $element['#value'] == $element['#return_value']) {
+  if (isset($element['#return_value']) && isset($element['#value']) && $element['#value'] == $element['#return_value']) {
     $element['#attributes']['checked'] = 'checked';
   }
   _form_set_class($element, array('form-radio'));
@@ -3041,9 +3066,7 @@ function form_process_radios($element) {
         // The key is sanitized in drupal_attributes() during output from the
         // theme function.
         '#return_value' => $key,
-        // Use default or FALSE. A value of FALSE means that the radio button is
-        // not 'checked'.
-        '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : FALSE,
+        '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : NULL,
         '#attributes' => $element['#attributes'],
         '#parents' => $element['#parents'],
         '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)),
diff --git a/core/modules/field/modules/options/options.module b/core/modules/field/modules/options/options.module
index 3862ba7..5c52fde 100644
--- a/core/modules/field/modules/options/options.module
+++ b/core/modules/field/modules/options/options.module
@@ -106,16 +106,15 @@ function options_field_widget_form(&$form, &$form_state, $field, $instance, $lan
         $default_value = array(key($options));
       }
 
-      // If this is a single-value field, take the first default value, or
-      // default to NULL so that the form element is properly recognized as
-      // not having a default value.
+      // Radio buttons need a scalar value. Therefore, take the first default
+      // value if there is one, or use NULL otherwise (so that the form element
+      // is properly recognized as not having a default value).
       if (!$multiple) {
         $default_value = $default_value ? reset($default_value) : NULL;
       }
 
       $element += array(
         '#type' => $multiple ? 'checkboxes' : 'radios',
-        // Radio buttons need a scalar value.
         '#default_value' => $default_value,
         '#options' => $options,
       );
diff --git a/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php b/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php
index 20d1f56..1148efc 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php
@@ -162,9 +162,19 @@ class FormTest extends WebTestBase {
     $this->drupalPost('form-test/validate-required', $edit, 'Submit');
 
     // The only error messages that should appear are the relevant 'required'
-    // messages for each field.
+    // messages for each empty field.
     $expected = array();
-    foreach (array('textfield', 'checkboxes', 'select', 'radios') as $key) {
+    $empty_required_fields = array(
+      'textfield',
+      'checkboxes',
+      'select',
+      'radios',
+      'checkboxes_invalid_default_value',
+      'radios_invalid_default_value',
+      'checkboxes_default_value_false',
+      'radios_default_value_false',
+    );
+    foreach ($empty_required_fields as $key) {
       $expected[] = t('!name field is required.', array('!name' => $form[$key]['#title']));
     }
 
@@ -187,15 +197,54 @@ class FormTest extends WebTestBase {
       $this->fail(format_string("Found error message: @error", array('@error' => $not_found)));
     }
 
-    // Verify that input elements are still empty.
+    // Verify that the expected input elements are still empty.
     $this->assertFieldByName('textfield', '');
+    $this->assertFieldByName('textfield_optional', '');
     $this->assertNoFieldChecked('edit-checkboxes-foo');
     $this->assertNoFieldChecked('edit-checkboxes-bar');
-    $this->assertOptionSelected('edit-select', '');
+    $this->assertNoFieldChecked('edit-checkboxes-optional-foo');
+    $this->assertNoFieldChecked('edit-checkboxes-optional-bar');
     $this->assertNoFieldChecked('edit-radios-foo');
     $this->assertNoFieldChecked('edit-radios-bar');
     $this->assertNoFieldChecked('edit-radios-optional-foo');
     $this->assertNoFieldChecked('edit-radios-optional-bar');
+    $this->assertNoFieldChecked('edit-checkboxes-invalid-default-value-foo');
+    $this->assertNoFieldChecked('edit-checkboxes-invalid-default-value-bar');
+    $this->assertNoFieldChecked('edit-checkboxes-optional-invalid-default-value-foo');
+    $this->assertNoFieldChecked('edit-checkboxes-optional-invalid-default-value-bar');
+    $this->assertNoFieldChecked('edit-radios-invalid-default-value-foo');
+    $this->assertNoFieldChecked('edit-radios-invalid-default-value-bar');
+    $this->assertNoFieldChecked('edit-radios-optional-invalid-default-value-foo');
+    $this->assertNoFieldChecked('edit-radios-optional-invalid-default-value-bar');
+    $this->assertNoFieldChecked('edit-checkboxes-default-value-false-foo');
+    $this->assertNoFieldChecked('edit-checkboxes-default-value-false-bar');
+    $this->assertNoFieldChecked('edit-checkboxes-optional-default-value-false-foo');
+    $this->assertNoFieldChecked('edit-checkboxes-optional-default-value-false-bar');
+    $this->assertNoFieldChecked('edit-radios-default-value-false-foo');
+    $this->assertNoFieldChecked('edit-radios-default-value-false-bar');
+    $this->assertNoFieldChecked('edit-radios-optional-default-value-false-foo');
+    $this->assertNoFieldChecked('edit-radios-optional-default-value-false-bar');
+
+    // Select elements behave differently, since the browser always submits a
+    // value for them. So rather than the element being empty, we expect the
+    // first option in the list to be selected.
+    $this->assertOptionSelected('edit-select', '');
+    $this->assertOptionSelected('edit-select-optional', 'foo');
+
+    // Verify that elements with a valid default value still have that default
+    // value selected.
+    $this->assertFieldByName('textfield_valid_default_value', 'bar');
+    $this->assertFieldByName('textfield_optional_valid_default_value', 'bar');
+    $this->assertNoFieldChecked('edit-checkboxes-valid-default-value-foo');
+    $this->assertFieldChecked('edit-checkboxes-valid-default-value-bar');
+    $this->assertNoFieldChecked('edit-checkboxes-optional-valid-default-value-foo');
+    $this->assertFieldChecked('edit-checkboxes-optional-valid-default-value-bar');
+    $this->assertOptionSelected('edit-select-valid-default-value', 'bar');
+    $this->assertOptionSelected('edit-select-optional-valid-default-value', 'bar');
+    $this->assertNoFieldChecked('edit-radios-valid-default-value-foo');
+    $this->assertFieldChecked('edit-radios-valid-default-value-bar');
+    $this->assertNoFieldChecked('edit-radios-optional-valid-default-value-foo');
+    $this->assertFieldChecked('edit-radios-optional-valid-default-value-bar');
 
     // Submit again with required fields set and verify that there are no
     // error messages.
@@ -204,6 +253,10 @@ class FormTest extends WebTestBase {
       'checkboxes[foo]' => TRUE,
       'select' => 'foo',
       'radios' => 'bar',
+      'checkboxes_invalid_default_value[foo]' => TRUE,
+      'radios_invalid_default_value' => 'foo',
+      'checkboxes_default_value_false[bar]' => TRUE,
+      'radios_default_value_false' => 'bar',
     );
     $this->drupalPost(NULL, $edit, 'Submit');
     $this->assertNoFieldByXpath('//div[contains(@class, "error")]', FALSE, 'No error message is displayed when all required fields are filled.');
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 f681c75..5977106 100644
--- a/core/modules/system/tests/modules/form_test/form_test.module
+++ b/core/modules/system/tests/modules/form_test/form_test.module
@@ -412,25 +412,51 @@ function form_test_validate_form_validate(&$form, &$form_state) {
  * Form constructor to test the #required property.
  */
 function form_test_validate_required_form($form, &$form_state) {
-  $options = drupal_map_assoc(array('foo', 'bar'));
-
+  // Define the list of options we will use in this test. To catch any bugs
+  // which might result from code that confuses the keys and values of the
+  // #options array, we set the displayed value of the 'bar' option to 'none'
+  // (the same value as the machine-readable name of the invalid option we will
+  // test with).
+  $options = array(
+    'foo' => 'foo',
+    'bar' => 'none',
+  );
+  $invalid_option = 'none';
+  $valid_option = 'bar';
+
+  // Test form elements that do not have a default value set. When the form is
+  // submitted with no input, only the required elements should produce errors.
   $form['textfield'] = array(
     '#type' => 'textfield',
     '#title' => 'Textfield',
     '#required' => TRUE,
   );
+  $form['textfield_optional'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Textfield (optional)',
+  );
   $form['checkboxes'] = array(
     '#type' => 'checkboxes',
     '#title' => 'Checkboxes',
     '#options' => $options,
     '#required' => TRUE,
   );
+  $form['checkboxes_optional'] = array(
+    '#type' => 'checkboxes',
+    '#title' => 'Checkboxes (optional)',
+    '#options' => $options,
+  );
   $form['select'] = array(
     '#type' => 'select',
     '#title' => 'Select',
     '#options' => $options,
     '#required' => TRUE,
   );
+  $form['select_optional'] = array(
+    '#type' => 'select',
+    '#title' => 'Select (optional)',
+    '#options' => $options,
+  );
   $form['radios'] = array(
     '#type' => 'radios',
     '#title' => 'Radios',
@@ -442,6 +468,120 @@ function form_test_validate_required_form($form, &$form_state) {
     '#title' => 'Radios (optional)',
     '#options' => $options,
   );
+
+  // Test form elements that have a valid default value set. These should not
+  // produce any errors when the form is submitted.
+  $form['textfield_valid_default_value'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Textfield (with a valid default_value)',
+    '#required' => TRUE,
+    '#default_value' => $valid_option,
+  );
+  $form['textfield_optional_valid_default_value'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Textfield (optional, with a valid default_value)',
+    '#default_value' => $valid_option,
+  );
+  $form['checkboxes_valid_default_value'] = array(
+    '#type' => 'checkboxes',
+    '#title' => 'Checkboxes (with a valid default_value)',
+    '#options' => $options,
+    '#required' => TRUE,
+    '#default_value' => array($valid_option),
+  );
+  $form['checkboxes_optional_valid_default_value'] = array(
+    '#type' => 'checkboxes',
+    '#title' => 'Checkboxes (optional, with a valid default_value)',
+    '#options' => $options,
+    '#default_value' => array($valid_option),
+  );
+  $form['select_valid_default_value'] = array(
+    '#type' => 'select',
+    '#title' => 'Select (with a valid default_value)',
+    '#options' => $options,
+    '#required' => TRUE,
+    '#default_value' => $valid_option,
+  );
+  $form['select_optional_valid_default_value'] = array(
+    '#type' => 'select',
+    '#title' => 'Select (optional, with a valid default_value)',
+    '#options' => $options,
+    '#default_value' => $valid_option,
+  );
+  $form['radios_valid_default_value'] = array(
+    '#type' => 'radios',
+    '#title' => 'Radios (with a valid default_value)',
+    '#options' => $options,
+    '#required' => TRUE,
+    '#default_value' => $valid_option,
+  );
+  $form['radios_optional_valid_default_value'] = array(
+    '#type' => 'radios',
+    '#title' => 'Radios (optional, with a valid default_value)',
+    '#options' => $options,
+    '#default_value' => $valid_option,
+  );
+
+  // For radios and checkboxes, we also test form elements whose default value
+  // does not appear in the list of #options. The form API should treat this as
+  // if there was no default value set at all.
+  $form['checkboxes_invalid_default_value'] = array(
+    '#type' => 'checkboxes',
+    '#title' => 'Checkboxes (with an invalid default value)',
+    '#options' => $options,
+    '#required' => TRUE,
+    '#default_value' => array($invalid_option),
+  );
+  $form['checkboxes_optional_invalid_default_value'] = array(
+    '#type' => 'checkboxes',
+    '#title' => 'Checkboxes (optional, with an invalid default value)',
+    '#options' => $options,
+    '#default_value' => array($invalid_option),
+  );
+  $form['radios_invalid_default_value'] = array(
+    '#type' => 'radios',
+    '#title' => 'Radios (with an invalid default value)',
+    '#options' => $options,
+    '#required' => TRUE,
+    '#default_value' => $invalid_option,
+  );
+  $form['radios_optional_invalid_default_value'] = array(
+    '#type' => 'radios',
+    '#title' => 'Radios (optional, with an invalid default value)',
+    '#options' => $options,
+    '#default_value' => $invalid_option,
+  );
+
+  // For radios and checkboxes, we also test the special case where the default
+  // value is FALSE. Since FALSE is not one of the #options, it should behave
+  // the same way as the invalid default value above.
+  $form['checkboxes_default_value_false'] = array(
+    '#type' => 'checkboxes',
+    '#title' => 'Checkboxes (with a default value of FALSE)',
+    '#options' => $options,
+    '#required' => TRUE,
+    '#default_value' => array(FALSE),
+  );
+  $form['checkboxes_optional_default_value_false'] = array(
+    '#type' => 'checkboxes',
+    '#title' => 'Checkboxes (optional, with a default value of FALSE)',
+    '#options' => $options,
+    '#default_value' => array(FALSE),
+  );
+  $form['radios_default_value_false'] = array(
+    '#type' => 'radios',
+    '#title' => 'Radios (with a default value of FALSE)',
+    '#options' => $options,
+    '#required' => TRUE,
+    '#default_value' => FALSE,
+  );
+  $form['radios_optional_default_value_false'] = array(
+    '#type' => 'radios',
+    '#title' => 'Radios (optional, with a default value of FALSE)',
+    '#options' => $options,
+    '#default_value' => FALSE,
+  );
+
   $form['actions'] = array('#type' => 'actions');
   $form['actions']['submit'] = array('#type' => 'submit', '#value' => 'Submit');
   return $form;
@@ -451,7 +591,23 @@ function form_test_validate_required_form($form, &$form_state) {
  * Form submission handler for form_test_validate_required_form().
  */
 function form_test_validate_required_form_submit($form, &$form_state) {
-  drupal_set_message('The form_test_validate_required_form form was submitted successfully.');
+  // Ensure that for form elements which have an array of allowed #options,
+  // there are never any submitted values from outside that array.
+  $errors = FALSE;
+  foreach ($form_state['values'] as $key => $value) {
+    if (isset($value) && !empty($form[$key]['#options'])) {
+      $invalid_array = is_array($value) && array_diff(array_filter($value), array_keys($form[$key]['#options']));
+      $invalid_scalar = !is_array($value) && !in_array($value, array_keys($form[$key]['#options']));
+      if ($invalid_array || $invalid_scalar) {
+        $errors = TRUE;
+        drupal_set_message(format_string('Invalid option was submitted for @element.', array('@element' => $key)), 'error');
+      }
+    }
+  }
+
+  if (!$errors) {
+    drupal_set_message('The form_test_validate_required_form form was submitted successfully.');
+  }
 }
 
 /**
