diff --git a/core/lib/Drupal/Component/Utility/NestedArray.php b/core/lib/Drupal/Component/Utility/NestedArray.php
index c9feb599d1..b1809848c7 100644
--- a/core/lib/Drupal/Component/Utility/NestedArray.php
+++ b/core/lib/Drupal/Component/Utility/NestedArray.php
@@ -69,7 +69,7 @@ class NestedArray {
   public static function &getValue(array &$array, array $parents, &$key_exists = NULL) {
     $ref = &$array;
     foreach ($parents as $parent) {
-      if (is_array($ref) && \array_key_exists($parent, $ref)) {
+      if (is_array($ref) && (isset($ref[$parent]) || array_key_exists($parent, $ref))) {
         $ref = &$ref[$parent];
       }
       else {
@@ -138,19 +138,40 @@ public static function &getValue(array &$array, array $parents, &$key_exists = N
    * @param bool $force
    *   (optional) If TRUE, the value is forced into the structure even if it
    *   requires the deletion of an already existing non-array parent value. If
-   *   FALSE, PHP throws an error if trying to add into a value that is not an
-   *   array. Defaults to FALSE.
+   *   FALSE, an exception will be thrown if value is unable to be set.
+   *   Defaults to FALSE.
+   *
+   * @throws \InvalidArgumentException
+   *   If value doesn't match parents structure and value can't be set.
    *
    * @see NestedArray::unsetValue()
    * @see NestedArray::getValue()
    */
   public static function setValue(array &$array, array $parents, $value, $force = FALSE) {
     $ref = &$array;
-    foreach ($parents as $parent) {
+    foreach ($parents as $i => $parent) {
       // PHP auto-creates container arrays and NULL entries without error if $ref
       // is NULL, but throws an error if $ref is set, but not an array.
-      if ($force && isset($ref) && !is_array($ref)) {
-        $ref = [];
+      if (isset($ref) && !is_array($ref)) {
+        if ($force) {
+          $ref = [];
+        }
+        elseif ($ref === '') {
+          // Up to PHP 7.0, this case was treated like NULL or empty array.
+          // In PHP 7.1 and greater, this case would trigger an error.
+          // The old behavior is replicated here for BC.
+          // @see https://www.drupal.org/project/drupal/issues/2682997#comment-13031609
+          $ref = [];
+        }
+        else {
+          // Throw an exception with a usable message instead of triggering a
+          // fatal error. This allows code to catch and be resilient if it needs
+          // to while also safely failing out in a backwards compatible way.
+          $parents_str = '[' . implode('][', $parents) . ']';
+          $parents_until_here_str = '[' . implode('][', array_slice($parents, 0, $i)) . ']';
+          $bad_value = var_export($ref, TRUE);
+          throw new \InvalidArgumentException("Trying to set nested value at $parents_str, but non-array value $bad_value found at $parents_until_here_str");
+        }
       }
       $ref = &$ref[$parent];
     }
diff --git a/core/lib/Drupal/Core/Config/ConfigBase.php b/core/lib/Drupal/Core/Config/ConfigBase.php
index 744af363b1..69240675e6 100644
--- a/core/lib/Drupal/Core/Config/ConfigBase.php
+++ b/core/lib/Drupal/Core/Config/ConfigBase.php
@@ -175,7 +175,7 @@ public function setData(array $data) {
    *   The configuration object.
    *
    * @throws \Drupal\Core\Config\ConfigValueException
-   *   If $value is an array and any of its keys in any depth contains a dot.
+   *   If $value is an array any keys contains a dot or value doesn't match key structure.
    */
   public function set($key, $value) {
     $value = $this->castSafeStrings($value);
@@ -189,7 +189,12 @@ public function set($key, $value) {
       $this->data[$key] = $value;
     }
     else {
+      try {
       NestedArray::setValue($this->data, $parts, $value);
+    }
+      catch (\InvalidArgumentException $e) {
+        throw new ConfigValueException('Value does not match expected structure.', 0, $e);
+      }
     }
     return $this;
   }
diff --git a/core/modules/views/tests/src/Functional/Plugin/ExposedFormCheckboxesTest.php b/core/modules/views/tests/src/Functional/Plugin/ExposedFormCheckboxesTest.php
index 997e9b207a..5d603d626f 100644
--- a/core/modules/views/tests/src/Functional/Plugin/ExposedFormCheckboxesTest.php
+++ b/core/modules/views/tests/src/Functional/Plugin/ExposedFormCheckboxesTest.php
@@ -166,6 +166,12 @@ public function testExposedIsAllOfFilter() {
     // Ensure only nodes tagged with $tid are displayed.
     $this->assertSession()->elementsCount('xpath', "//div[contains(@class, 'views-row')]", 2);
     $this->assertSession()->pageTextNotContains('The submitted value in the Reference Field element is not allowed.');
+
+    // Query ensures we can't break the form with invalid input.
+    $this->drupalGet('test_exposed_form_checkboxes', ['query' => ['tid' => '']]);
+    $this->assertSession()->statusCodeEquals(200);
+    $this->drupalGet('test_exposed_form_checkboxes', ['query' => ['tid' => 'asdf']]);
+    $this->assertSession()->statusCodeEquals(200);
   }
 
 }
diff --git a/core/tests/Drupal/Tests/Component/Utility/NestedArrayTest.php b/core/tests/Drupal/Tests/Component/Utility/NestedArrayTest.php
index f6f6873948..400a35b250 100644
--- a/core/tests/Drupal/Tests/Component/Utility/NestedArrayTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/NestedArrayTest.php
@@ -87,6 +87,28 @@ public function testSetValue() {
     NestedArray::setValue($this->form, $this->parents, $new_value);
     $this->assertSame('New value', $this->form['details']['element']['#value'], 'Changed nested element value found.');
     $this->assertTrue($this->form['details']['element']['#required'], 'New nested element value found.');
+
+    // BC compatible empty string fails quietly.
+    $values = ['x' => ''];
+    NestedArray::setValue($values, ['x', 1], NULL);
+  }
+
+  /**
+   * Tests failures when trying to set nested values.
+   *
+   * @covers ::setValue
+   * @dataProvider provideInvalidSetValues
+   */
+  public function testSetValueException($values, $parents) {
+    $this->expectException(\InvalidArgumentException::class);
+    NestedArray::setValue($values, $parents, NULL);
+  }
+
+  public function provideInvalidSetValues() {
+    return [
+      [['x' => '1'], ['x', 1]],
+      [['x' => 1], ['x', 1]],
+    ];
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Config/ConfigTest.php b/core/tests/Drupal/Tests/Core/Config/ConfigTest.php
index 1bdc3cbd28..e7a7fbeff7 100644
--- a/core/tests/Drupal/Tests/Core/Config/ConfigTest.php
+++ b/core/tests/Drupal/Tests/Core/Config/ConfigTest.php
@@ -60,8 +60,6 @@ class ConfigTest extends UnitTestCase {
    * {@inheritdoc}
    */
   protected function setUp(): void {
-    parent::setUp();
-
     $this->storage = $this->createMock('Drupal\Core\Config\StorageInterface');
     $this->eventDispatcher = $this->createMock('Symfony\Contracts\EventDispatcher\EventDispatcherInterface');
     $this->typedConfig = $this->createMock('\Drupal\Core\Config\TypedConfigManagerInterface');
@@ -274,9 +272,8 @@ public function testSetValidation() {
   public function testSetIllegalOffsetValue() {
     // Set a single value.
     $this->config->set('testData', 1);
-
-    // Attempt to treat the single value as a nested item.
-    $this->expectError();
+    $this->expectException(ConfigValueException::class);
+    $this->expectExceptionMessage('Value does not match expected structure.');
     $this->config->set('testData.illegalOffset', 1);
   }
 
