diff --git a/core/includes/form.inc b/core/includes/form.inc
index 3469377..29377eb 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -2281,6 +2281,48 @@ function form_type_checkboxes_value($element, $input = FALSE) {
 }
 
 /**
+ * Form value callback: Determines the value for a #type radios 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.
+ *
+ * @return
+ *   The data that will appear in the $element_state['values'] collection for
+ *   this element.
+ */
+function form_type_radios_value(&$element, $input = FALSE) {
+  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'])) {
+    return $element['#default_value'];
+  }
+}
+
+/**
  * Helper function to determine the value for a tableselect form element.
  *
  * @param $element
@@ -2724,7 +2766,8 @@ 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']) {
+  // Do a type-agnostic comparison to prevent bogus matches (e.g., 0 == "").
+  if (isset($element['#return_value']) && $element['#value'] === $element['#return_value']) {
     $element['#attributes']['checked'] = 'checked';
   }
   _form_set_class($element, array('form-radio'));
diff --git a/core/modules/field/modules/options/options.module b/core/modules/field/modules/options/options.module
index d4d05ec..04b88d8 100644
--- a/core/modules/field/modules/options/options.module
+++ b/core/modules/field/modules/options/options.module
@@ -102,10 +102,18 @@ function options_field_widget_form(&$form, &$form_state, $field, $instance, $lan
         reset($options);
         $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.
+      if (!$multiple) {
+        $default_value = $default_value ? reset($default_value) : NULL;
+      }
+
       $element += array(
         '#type' => $multiple ? 'checkboxes' : 'radios',
         // Radio buttons need a scalar value.
-        '#default_value' => $multiple ? $default_value : reset($default_value),
+        '#default_value' => $default_value,
         '#options' => $options,
       );
       break;
diff --git a/core/modules/field/tests/field.test b/core/modules/field/tests/field.test
index d628003..61d6981 100644
--- a/core/modules/field/tests/field.test
+++ b/core/modules/field/tests/field.test
@@ -1460,6 +1460,51 @@ class FieldFormTestCase extends FieldTestCase {
     // Test with several multiple fields in a form
   }
 
+  /**
+   * Tests widget handling of multiple required radios.
+   */
+  function testFieldFormMultivalueWithRequiredRadio() {
+    // Create a multivalue test field.
+    $this->field = $this->field_unlimited;
+    $this->field_name = $this->field['field_name'];
+    $this->instance['field_name'] = $this->field_name;
+    field_create_field($this->field);
+    field_create_instance($this->instance);
+    $langcode = LANGUAGE_NONE;
+
+    // Add a required radio field.
+    field_create_field(array(
+      'field_name' => 'required_radio_test',
+      'type' => 'list_text',
+      'settings' => array(
+        'allowed_values' => array('yes' => 'yes', 'no' => 'no'),
+      ),
+    ));
+    field_create_instance(array(
+      'field_name' => 'required_radio_test',
+      'entity_type' => 'test_entity',
+      'bundle' => 'test_bundle',
+      'required' => TRUE,
+      'widget' => array(
+        'type' => 'options_buttons',
+      ),
+    ));
+
+    // Display creation form.
+    $this->drupalGet('test-entity/add/test-bundle');
+
+    // Press the 'Add more' button.
+    $this->drupalPost(NULL, array(), t('Add another item'));
+
+    // Verify that no error is thrown by the radio element.
+    $this->assertNoFieldByXpath('//div[contains(@class, "error")]', FALSE, 'No error message is displayed.');
+
+    // Verify that the widget is added.
+    $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', 'Widget 1 is displayed');
+    $this->assertFieldByName("{$this->field_name}[$langcode][1][value]", '', 'New widget is displayed');
+    $this->assertNoField("{$this->field_name}[$langcode][2][value]", 'No extraneous widget is displayed');
+  }
+
   function testFieldFormJSAddMore() {
     $this->field = $this->field_unlimited;
     $this->field_name = $this->field['field_name'];
diff --git a/core/modules/simpletest/tests/form.test b/core/modules/simpletest/tests/form.test
index 94dfa87..f7abc34 100644
--- a/core/modules/simpletest/tests/form.test
+++ b/core/modules/simpletest/tests/form.test
@@ -122,6 +122,72 @@ class FormsTestCase extends DrupalWebTestCase {
   }
 
   /**
+   * Tests validation for required checkbox, select, and radio elements.
+   *
+   * Submits a test form containing several types of form elements. The form
+   * is submitted twice, first without values for required fields and then
+   * with values. Each submission is checked for relevant error messages.
+   *
+   * @see form_test_validate_required_form()
+   */
+  function testRequiredCheckboxesRadio() {
+    $form = $form_state = array();
+    $form = form_test_validate_required_form($form, $form_state);
+
+    // Attempt to submit the form with no required fields set.
+    $edit = array();
+    $this->drupalPost('form-test/validate-required', $edit, 'Submit');
+
+    // The only error messages that should appear are the relevant 'required'
+    // messages for each field.
+    $expected = array();
+    foreach (array('textfield', 'checkboxes', 'select', 'radios') as $key) {
+      $expected[] = t('!name field is required.', array('!name' => $form[$key]['#title']));
+    }
+
+    // Check the page for error messages.
+    $errors = $this->xpath('//div[contains(@class, "error")]//li');
+    foreach ($errors as $error) {
+      $expected_key = array_search($error[0], $expected);
+      // If the error message is not one of the expected messages, fail.
+      if ($expected_key === FALSE) {
+        $this->fail(format_string("Unexpected error message: @error", array('@error' => $error[0])));
+      }
+      // Remove the expected message from the list once it is found.
+      else {
+        unset($expected[$expected_key]);
+      }
+    }
+
+    // Fail if any expected messages were not found.
+    foreach ($expected as $not_found) {
+      $this->fail(format_string("Found error message: @error", array('@error' => $not_found)));
+    }
+
+    // Verify that input elements are still empty.
+    $this->assertFieldByName('textfield', '');
+    $this->assertNoFieldChecked('edit-checkboxes-foo');
+    $this->assertNoFieldChecked('edit-checkboxes-bar');
+    $this->assertOptionSelected('edit-select', '');
+    $this->assertNoFieldChecked('edit-radios-foo');
+    $this->assertNoFieldChecked('edit-radios-bar');
+    $this->assertNoFieldChecked('edit-radios-optional-foo');
+    $this->assertNoFieldChecked('edit-radios-optional-bar');
+
+    // Submit again with required fields set and verify that there are no
+    // error messages.
+    $edit = array(
+      'textfield' => $this->randomString(),
+      'checkboxes[foo]' => TRUE,
+      'select' => 'foo',
+      'radios' => 'bar',
+    );
+    $this->drupalPost(NULL, $edit, 'Submit');
+    $this->assertNoFieldByXpath('//div[contains(@class, "error")]', FALSE, 'No error message is displayed when all required fields are filled.');
+    $this->assertRaw("The form_test_validate_required_form form was submitted successfully.", 'Validation form submitted successfully.');
+  }
+
+  /**
    * Test default value handling for checkboxes.
    *
    * @see _form_test_checkbox()
@@ -424,6 +490,10 @@ class FormElementTestCase extends DrupalWebTestCase {
         $expected = array_shift($expected_values);
         $this->assertIdentical((string) $element['value'], $expected);
       }
+      // Additionally verify that there is only one option checked.
+      $elements = $this->xpath('//input[@type=:type and @checked="checked"]', array(':type' => $type));
+      $this->assertEqual(count($elements), 1);
+      $this->assertIdentical((string) $elements[0]['value'], '0');
     }
 
     // Enable customized option sub-elements.
diff --git a/core/modules/simpletest/tests/form_test.module b/core/modules/simpletest/tests/form_test.module
index 5e3fb17..2946195 100644
--- a/core/modules/simpletest/tests/form_test.module
+++ b/core/modules/simpletest/tests/form_test.module
@@ -23,6 +23,13 @@ function form_test_menu() {
     'access arguments' => array('access content'),
     'type' => MENU_CALLBACK,
   );
+  $items['form-test/validate-required'] = array(
+    'title' => 'Form #required validation',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('form_test_validate_required_form'),
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+  );
   $items['form-test/limit-validation-errors'] = array(
     'title' => 'Form validation with some error suppression',
     'page callback' => 'drupal_get_form',
@@ -346,6 +353,52 @@ 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'));
+
+  $form['textfield'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Textfield',
+    '#required' => TRUE,
+  );
+  $form['checkboxes'] = array(
+    '#type' => 'checkboxes',
+    '#title' => 'Checkboxes',
+    '#options' => $options,
+    '#required' => TRUE,
+  );
+  $form['select'] = array(
+    '#type' => 'select',
+    '#title' => 'Select',
+    '#options' => $options,
+    '#required' => TRUE,
+  );
+  $form['radios'] = array(
+    '#type' => 'radios',
+    '#title' => 'Radios',
+    '#options' => $options,
+    '#required' => TRUE,
+  );
+  $form['radios_optional'] = array(
+    '#type' => 'radios',
+    '#title' => 'Radios (optional)',
+    '#options' => $options,
+  );
+  $form['actions'] = array('#type' => 'actions');
+  $form['actions']['submit'] = array('#type' => 'submit', '#value' => 'Submit');
+  return $form;
+}
+
+/**
+ * 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.');
+}
+
+/**
  * Builds a simple form with a button triggering partial validation.
  */
 function form_test_limit_validation_errors_form($form, &$form_state) {
@@ -1049,6 +1102,7 @@ function form_test_checkboxes_radios($form, &$form_state, $customize = FALSE) {
   $form['checkboxes'] = array(
     '#type' => 'checkboxes',
     '#title' => 'Checkboxes',
+    '#default_value' => array(0),
     '#options' => array(
       0 => 'Zero',
       'foo' => 'Foo',
@@ -1073,6 +1127,7 @@ function form_test_checkboxes_radios($form, &$form_state, $customize = FALSE) {
   $form['radios'] = array(
     '#type' => 'radios',
     '#title' => 'Radios',
+    '#default_value' => 0,
     '#options' => array(
       0 => 'Zero',
       'foo' => 'Foo',
