diff --git a/core/includes/form.inc b/core/includes/form.inc
index eee8be1..38a78be 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -2337,27 +2337,31 @@ function form_type_checkboxes_value($element, $input = FALSE) {
  *   this element.
  */
 function form_type_radios_value(&$element, $input = FALSE) {
+  // 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;
+  }
+
+  // If there was a user submission, force standard form API validation to run
+  // even if nothing was submitted. However, make an exception for elements
+  // with empty default values; this preserves the ability of modules to set
+  // #default_value to, e.g., FALSE (rather than NULL).
+  if (!isset($input) && !empty($element['#default_value'])) {
+    $element['#needs_validation'] = TRUE;
+  }
+
+  // Besides the above, the form API's standard value handling is correct for
+  // radios, so it's not necessary to return anything from this function.
+  // However, for clarity (and backwards compatibility) we still ensure that
+  // the value which will be used for the element is returned.
   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'])) {
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 6225bb9..65a265a 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php
@@ -196,6 +196,8 @@ class FormTest extends WebTestBase {
     $this->assertNoFieldChecked('edit-radios-bar');
     $this->assertNoFieldChecked('edit-radios-optional-foo');
     $this->assertNoFieldChecked('edit-radios-optional-bar');
+    $this->assertNoFieldChecked('edit-radios-optional-default-value-false-foo');
+    $this->assertNoFieldChecked('edit-radios-optional-default-value-false-bar');
 
     // Submit again with required fields set and verify that there are no
     // error messages.
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 86e810d..b89426c 100644
--- a/core/modules/system/tests/modules/form_test/form_test.module
+++ b/core/modules/system/tests/modules/form_test/form_test.module
@@ -458,6 +458,12 @@ function form_test_validate_required_form($form, &$form_state) {
     '#title' => 'Radios (optional)',
     '#options' => $options,
   );
+  $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;
