diff --git a/core/includes/form.inc b/core/includes/form.inc
index a12fb34..8b1a7d2 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -2239,6 +2239,42 @@ function form_type_checkbox_value($element, $input = FALSE) {
 }
 
 /**
+ * Form value callback to determine the value for a radios form element.
+ *
+ * @param $element
+ *   The form element whose value is being populated.
+ * @param $input
+ *   The incoming input to populate the form element. If this is FALSE, the
+ *   element's default value is returned.
+ *
+ * @return
+ *   The data that will appear in the $element_state['values'] collection for
+ *   this element. If NULL is returned and #has_garbage_value is not set then
+ *   it will automatically attempt to use default value or ''.
+ */
+function form_type_radios_value(&$element, $input = FALSE) {
+  // If there is data coming from form submission.
+  if ($input !== FALSE) {
+    // Check if the user submission had no value for this element.
+    if (!isset($input)) {
+      // Flag as a garbage value so that form builder will not automatically
+      // replace the our NULL with an empty string.
+      $element['#has_garbage_value'] = TRUE;
+      // There was a user submission so validation is a must. This way, the
+      // required checker will produce an error as necessary.
+      $element['#needs_validation'] = TRUE;
+    }
+    // The value stays the same, but the flags above will process it properly.
+    return $input;
+  }
+
+  // When $input is FALSE, return the default if it exists.
+  if (isset($element['#default_value'])) {
+    return $element['#default_value'];
+  }
+}
+
+/**
  * Helper function to determine the value for a checkboxes form element.
  *
  * @param $element
@@ -2954,7 +2990,9 @@ function form_process_radios($element) {
         // The key is sanitized in drupal_attributes() during output from the
         // theme function.
         '#return_value' => $key,
-        '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : NULL,
+        // 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,
         '#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 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/simpletest/tests/form.test b/core/modules/simpletest/tests/form.test
index 70ebf86..222549a 100644
--- a/core/modules/simpletest/tests/form.test
+++ b/core/modules/simpletest/tests/form.test
@@ -122,6 +122,50 @@ class FormsTestCase extends DrupalWebTestCase {
   }
 
   /**
+   * Tests #required checkboxes and radios.
+   *
+   * Programmatically submits a test form containing several types of input
+   * elements. The form is submitted twice, once with values and once without,
+   * and each is checked for the proper #required error messages.
+   *
+   * @see form_test_validate_required_form()
+   * @see form_test_element_validate()
+   */
+  function testRequiredCheckboxesRadio() {
+    $form = $form_state = array();
+    $form = form_test_validate_required_form($form, $form_state);
+
+    // Verify each error message shows when required value not selected.
+    $edit = array();
+    $this->drupalPost('form-test/validate-required', $edit, 'Submit');
+
+    foreach (array('textfield', 'checkboxes', 'select', 'radios') as $key) {
+      $this->assertText(t('!name field is required.', array('!name' => $form[$key]['#title'])));
+    }
+
+    // No invalid options were submitted, only empty ones. Therefore, the error
+    // message about disallowed values should not appear, nor should our custom
+    // validator report that the value is not found.
+    $this->assertNoText(t('illegal choice'));
+    $this->assertNoText(t('Value is not allowed'));
+
+    // Verify that no errors appear with valid values.
+    $edit = array(
+      'textfield' => $this->randomString(),
+      'checkboxes[foo]' => TRUE,
+      'select' => 'foo',
+      'radios' => 'bar',
+    );
+    $this->drupalPost('form-test/validate-required', $edit, 'Submit');
+
+    foreach (array('textfield', 'checkboxes', 'select', 'radios') as $key) {
+      $this->assertNoText(t('!name field is required.', array('!name' => $form[$key]['#title'])));
+    }
+    $this->assertNoText(t('illegal choice'));
+    $this->assertNoText(t('Value is not allowed'));
+  }
+
+  /**
    * Test default value handling for checkboxes.
    *
    * @see _form_test_checkbox()
diff --git a/core/modules/simpletest/tests/form_test.module b/core/modules/simpletest/tests/form_test.module
index 0393b18..6f57d92 100644
--- a/core/modules/simpletest/tests/form_test.module
+++ b/core/modules/simpletest/tests/form_test.module
@@ -23,6 +23,12 @@ 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,
+  );
   $items['form-test/limit-validation-errors'] = array(
     'title' => 'Form validation with some error suppression',
     'page callback' => 'drupal_get_form',
@@ -346,6 +352,79 @@ function form_test_validate_form_validate(&$form, &$form_state) {
 }
 
 /**
+ * Form constructor for a form to test the #required property.
+ */
+function form_test_validate_required_form($form, &$form_state) {
+  $form['textfield'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Textfield',
+    '#required' => TRUE,
+    '#element_validate' => array('form_test_element_validate'),
+  );
+  $form['checkboxes'] = array(
+    '#type' => 'checkboxes',
+    '#title' => 'Checkboxes',
+    '#options' => drupal_map_assoc(array('foo', 'bar')),
+    '#required' => TRUE,
+    '#element_validate' => array('form_test_element_validate'),
+  );
+  $form['select'] = array(
+    '#type' => 'select',
+    '#title' => 'Select',
+    '#options' => drupal_map_assoc(array('foo', 'bar')),
+    '#required' => TRUE,
+    '#element_validate' => array('form_test_element_validate'),
+  );
+  $form['radios'] = array(
+    '#type' => 'radios',
+    '#title' => 'Radios',
+    '#options' => drupal_map_assoc(array('foo', 'bar')),
+    '#required' => TRUE,
+    '#element_validate' => array('form_test_element_validate'),
+  );
+  $form['radios_optional'] = array(
+    '#type' => 'radios',
+    '#title' => 'Radios (optional)',
+    '#options' => drupal_map_assoc(array('foo', 'bar')),
+    '#element_validate' => array('form_test_element_validate'),
+  );
+  $form['actions'] = array('#type' => 'actions');
+  $form['actions']['submit'] = array('#type' => 'submit', '#value' => 'Submit');
+  return $form;
+}
+
+/**
+ * Form element validation handler for form_test_validate_required_form().
+ *
+ * Checks the form element for values that are not in the allowed values,
+ * using logic from _form_validate().
+ */
+function form_test_element_validate($element, &$form_state, $form) {
+  if (isset($element['#options']) && isset($element['#value'])) {
+    if ($element['#type'] == 'select') {
+      $options = form_options_flatten($element['#options']);
+    }
+    else {
+      $options = $element['#options'];
+    }
+    if (is_array($element['#value'])) {
+      $value = in_array($element['#type'], array('checkboxes', 'tableselect')) ? array_keys($element['#value']) : $element['#value'];
+      foreach ($value as $v) {
+        if (!isset($options[$v])) {
+          drupal_set_message("Value is not allowed for {$element['#name']}.");
+        }
+      }
+    }
+    elseif ($element['#type'] == 'select' && !$element['#multiple'] && $element['#required'] && !isset($element['#default_value']) && $element['#value'] === $element['#empty_value']) {
+      drupal_set_message("Value is not allowed for {$element['#name']}.");
+    }
+    elseif (!isset($options[$element['#value']])) {
+      drupal_set_message("Value is not allowed for {$element['#name']}.");
+    }
+  }
+}
+
+/**
  * Builds a simple form with a button triggering partial validation.
  */
 function form_test_limit_validation_errors_form($form, &$form_state) {
