diff --git a/core/modules/views/src/Plugin/views/filter/Date.php b/core/modules/views/src/Plugin/views/filter/Date.php
index 8ea3750..84a1c42 100644
--- a/core/modules/views/src/Plugin/views/filter/Date.php
+++ b/core/modules/views/src/Plugin/views/filter/Date.php
@@ -98,32 +98,23 @@ public function validateValidTime(&$form, FormStateInterface $form_state, $opera
   }
 
   /**
-   * Validate the build group options form.
+   * {@inheritdoc}
    */
-  protected function buildGroupValidate($form, FormStateInterface $form_state) {
-    // Special case to validate grouped date filters, this is because the
-    // $group['value'] array contains the type of filter (date or offset)
-    // and therefore the number of items the comparison has to be done
-    // against 'one' instead of 'zero'.
-    foreach ($form_state->getValue(array('options', 'group_info', 'group_items')) as $id => $group) {
-      if (empty($group['remove'])) {
-        // Check if the title is defined but value wasn't defined.
-        if (!empty($group['title'])) {
-          if ((!is_array($group['value']) && empty($group['value'])) || (is_array($group['value']) && count(array_filter($group['value'])) == 1)) {
-            $form_state->setError($form['group_info']['group_items'][$id]['value'], $this->t('The value is required if title for this item is defined.'));
-          }
-        }
-
-        // Check if the value is defined but title wasn't defined.
-        if ((!is_array($group['value']) && !empty($group['value'])) || (is_array($group['value']) && count(array_filter($group['value'])) > 1)) {
-          if (empty($group['title'])) {
-            $form_state->setError($form['group_info']['group_items'][$id]['title'], $this->t('The title is required if value for this item is defined.'));
-          }
-        }
-      }
+  protected function hasValidGroupedValue(array $group) {
+    if (!is_array($group['value']) || empty($group['value'])) {
+      return FALSE;
     }
-  }
 
+    // Special case when validating grouped date filters because the
+    // $group['value'] array contains the type of filter (date or offset) and
+    // therefore the number of items the comparison has to be done against is
+    // one greater.
+    $operators = $this->operators();
+    $expected = $operators[$group['operator']]['values'] + 1;
+    $actual = count(array_filter($group['value'], 'static::arrayFilterZero'));
+
+    return $actual == $expected;
+  }
 
   public function acceptExposedInput($input) {
     if (empty($this->options['exposed'])) {
diff --git a/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
index f7cbf1b..5daffe3 100644
--- a/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
+++ b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
@@ -618,6 +618,38 @@ public function validateExposeForm($form, FormStateInterface $form_state) {
   }
 
   /**
+   * Determines if the given grouped filter entry has a valid value.
+   *
+   * @param array $group
+   *   A group entry as defined by buildGroupForm().
+   *
+   * @return bool
+   */
+  protected function hasValidGroupedValue(array $group) {
+    $operators = $this->operators();
+    if ($operators[$group['operator']]['values'] == 0) {
+      // Some filters, such as "is empty," do not require a value to be
+      // specified in order to be valid entries.
+      return TRUE;
+    }
+    else {
+      if (is_string($group['value'])) {
+        return trim($group['value']) != '';
+      }
+      elseif (is_array($group['value'])) {
+        // Some filters allow multiple options to be selected (for example, node
+        // types). Ensure at least the minimum number of values is present for
+        // this entry to be considered valid.
+        $min_values = $operators[$group['operator']]['values'];
+        $actual_values = count(array_filter($group['value'], 'static::arrayFilterZero'));
+        return $actual_values >= $min_values;
+      }
+    }
+    return FALSE;
+  }
+
+
+  /**
    * Validate the build group options form.
    */
   protected function buildGroupValidate($form, FormStateInterface $form_state) {
@@ -627,26 +659,21 @@ protected function buildGroupValidate($form, FormStateInterface $form_state) {
     }
 
     if ($group_items = $form_state->getValue(array('options', 'group_info', 'group_items'))) {
-      $operators = $this->operators();
-
       foreach ($group_items as $id => $group) {
         if (empty($group['remove'])) {
-
-          // Check if the title is defined but value wasn't defined.
-          if (!empty($group['title']) && $operators[$group['operator']]['values'] > 0) {
-            if ((!is_array($group['value']) && trim($group['value']) == "") ||
-                (is_array($group['value']) && count(array_filter($group['value'], 'static::arrayFilterZero')) == 0)) {
-              $form_state->setError($form['group_info']['group_items'][$id]['value'], $this->t('The value is required if title for this item is defined.'));
+          $has_valid_value = $this->hasValidGroupedValue($group);
+          if ($has_valid_value && $group['title'] == '') {
+            $operators = $this->operators();
+            if ($operators[$group['operator']]['values'] == 0) {
+              $form_state->setError($form['group_info']['group_items'][$id]['title'], $this->t('A label is required for the specified operator.'));
             }
-          }
-
-          // Check if the value is defined but title wasn't defined.
-          if ((!is_array($group['value']) && trim($group['value']) != "") ||
-              (is_array($group['value']) && count(array_filter($group['value'], 'static::arrayFilterZero')) > 0)) {
-            if (empty($group['title'])) {
-              $form_state->setError($form['group_info']['group_items'][$id]['title'], $this->t('The title is required if value for this item is defined.'));
+            else {
+              $form_state->setError($form['group_info']['group_items'][$id]['title'], $this->t('A label is required if the value for this item is defined.'));
             }
           }
+          if (!$has_valid_value && $group['title'] != '') {
+            $form_state->setError($form['group_info']['group_items'][$id]['value'], $this->t('A value is required if the label for this item is defined.'));
+          }
         }
       }
     }
@@ -1457,19 +1484,6 @@ public function canGroup() {
    }
 
   /**
-   * Filter by no empty values, though allow to use "0".
-   *
-   * @param string $var
-   *   The variable to evaluate.
-   *
-   * @return bool
-   *   TRUE if the value is equal to an empty string, FALSE otherwise.
-   */
-  protected static function arrayFilterZero($var) {
-    return trim($var) != '';
-  }
-
-  /**
    * {@inheritdoc}
    */
   public function getCacheMaxAge() {
@@ -1506,6 +1520,21 @@ public function validate() {
     }
   }
 
+  /**
+   * Filter by no empty values, though allow the use of (string) "0".
+   *
+   * @param string $var
+   *   The variable to evaluate.
+   *
+   * @return bool
+   *   TRUE if the value is equal to an empty string, FALSE otherwise.
+   */
+  protected static function arrayFilterZero($var) {
+    if (is_int($var)) {
+      return $var != 0;
+    }
+    return trim($var) != '';
+  }
 }
 
 /**
diff --git a/core/modules/views_ui/src/Tests/ExposedFormUITest.php b/core/modules/views_ui/src/Tests/ExposedFormUITest.php
index a74300d..f50af9a 100644
--- a/core/modules/views_ui/src/Tests/ExposedFormUITest.php
+++ b/core/modules/views_ui/src/Tests/ExposedFormUITest.php
@@ -18,6 +18,20 @@ class ExposedFormUITest extends UITestBase {
    */
   public static $testViews = array('test_exposed_admin_ui');
 
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = array('node', 'views_ui', 'block', 'taxonomy', 'field_ui', 'datetime');
+
+  /**
+   * Array of error message strings raised by the grouped form.
+   *
+   * @var array
+   *
+   * @see FilterPluginBase::buildGroupValidate
+   */
+  protected $groupFormUiErrors = [];
+
   protected function setUp() {
     parent::setUp();
 
@@ -28,6 +42,11 @@ protected function setUp() {
     for ($i = 0; $i < 5; $i++) {
       $this->drupalCreateNode();
     }
+
+    // Error strings used in the grouped filter form validation.
+    $this->groupFormUiErrors['missing_value'] = t('A value is required if the label for this item is defined.');
+    $this->groupFormUiErrors['missing_title'] = t('A label is required if the value for this item is defined.');
+    $this->groupFormUiErrors['missing_title_empty_operator'] = t('A label is required for the specified operator.');
   }
 
   /**
@@ -51,8 +70,6 @@ function testExposedAdminUi() {
     $this->drupalPostForm('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type', $edit, t('Expose filter'));
     // Check the label of the expose button.
     $this->helperButtonHasLabel('edit-options-expose-button-button', t('Hide filter'));
-    // Check the label of the grouped exposed button
-    $this->helperButtonHasLabel('edit-options-group-button-button', t('Grouped filters'));
 
     // After exposing the filter, Operator and Value should be still here.
     $this->assertFieldById('edit-options-operator-in', '', 'Operator In exists');
@@ -76,6 +93,57 @@ function testExposedAdminUi() {
     $this->helperButtonHasLabel('edit-options-expose-button-button', t('Expose sort'));
     $this->assertNoFieldById('edit-options-expose-label', '', 'Make sure no label field is shown');
 
+    // Un-expose the filter.
+    $this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
+    $this->drupalPostForm(NULL, array(), t('Hide filter'));
+
+    // After Un-exposing the filter, Operator and Value should be shown again.
+    $this->assertFieldById('edit-options-operator-in', '', 'Operator In exists after hide filter');
+    $this->assertFieldById('edit-options-operator-not-in', '', 'Operator Not In exists after hide filter');
+    $this->assertFieldById('edit-options-value-page', '', 'Checkbox for Page exists after hide filter');
+    $this->assertFieldById('edit-options-value-article', '', 'Checkbox for Article exists after hide filter');
+
+    // Click the Expose sort button.
+    $edit = array();
+    $this->drupalPostForm('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/sort/created', $edit, t('Expose sort'));
+    // Check the label of the expose button.
+    $this->helperButtonHasLabel('edit-options-expose-button-button', t('Hide sort'));
+    $this->assertFieldById('edit-options-expose-label', '', 'Make sure a label field is shown');
+
+    // Test adding a new exposed sort criteria.
+    $view_id = $this->randomView()['id'];
+    $this->drupalGet("admin/structure/views/nojs/add-handler/$view_id/default/sort");
+    $this->drupalPostForm(NULL, ['name[node_field_data.created]' => 1], t('Add and configure @handler', ['@handler' => t('sort criteria')]));
+    $this->assertFieldByXPath('//input[@name="options[order]" and @checked="checked"]', 'ASC', 'The default order is set.');
+    // Change the order and expose the sort.
+    $this->drupalPostForm(NULL, ['options[order]' => 'DESC'], t('Apply'));
+    $this->drupalPostForm("admin/structure/views/nojs/handler/$view_id/default/sort/created", [], t('Expose sort'));
+    $this->assertFieldByXPath('//input[@name="options[order]" and @checked="checked"]', 'DESC');
+    $this->assertFieldByName('options[expose][label]', 'Authored on', 'The default label is set.');
+    // Change the label and save the view.
+    $edit = ['options[expose][label]' => $this->randomString()];
+    $this->drupalPostForm(NULL, $edit, t('Apply'));
+    $this->drupalPostForm(NULL, [], t('Save'));
+    // Check that the values were saved.
+    $display = View::load($view_id)->getDisplay('default');
+    $this->assertTrue($display['display_options']['sorts']['created']['exposed']);
+    $this->assertEqual($display['display_options']['sorts']['created']['expose'], ['label' => $edit['options[expose][label]']]);
+    $this->assertEqual($display['display_options']['sorts']['created']['order'], 'DESC');
+  }
+
+  /**
+   * Tests the admin interface of exposed grouped filters.
+   */
+  function testGroupedFilterAdminUi() {
+    $edit = array();
+
+    $this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
+
+    // Click the Expose filter button.
+    $this->drupalPostForm('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type', $edit, t('Expose filter'));
+    // Check the label of the grouped filters button.
+    $this->helperButtonHasLabel('edit-options-group-button-button', t('Grouped filters'));
+
     // Click the Grouped Filters button.
     $this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
     $this->drupalPostForm(NULL, array(), t('Grouped filters'));
@@ -91,82 +159,109 @@ function testExposedAdminUi() {
     // add more items to the list.
     $this->helperButtonHasLabel('edit-options-group-info-add-group', t('Add another item'));
 
-    // Create a grouped filter
+    // Validate a single entry for a grouped filter.
     $this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
     $edit = array();
     $edit["options[group_info][group_items][1][title]"] = 'Is Article';
     $edit["options[group_info][group_items][1][value][article]"] = 'article';
+    $this->drupalPostForm(NULL, $edit, t('Apply'));
+    $this->assertUrl('admin/structure/views/view/test_exposed_admin_ui/edit/default');
+    $this->assertNoGroupedFilterErrors();
 
+    // Validate multiple entries for grouped filters.
+    $this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
+    $edit = array();
+    $edit["options[group_info][group_items][1][title]"] = 'Is Article';
+    $edit["options[group_info][group_items][1][value][article]"] = 'article';
     $edit["options[group_info][group_items][2][title]"] = 'Is Page';
-    $edit["options[group_info][group_items][2][value][page]"] = TRUE;
-
+    $edit["options[group_info][group_items][2][value][page]"] = 'page';
     $edit["options[group_info][group_items][3][title]"] = 'Is Page and Article';
-    $edit["options[group_info][group_items][3][value][article]"] = TRUE;
-    $edit["options[group_info][group_items][3][value][page]"] = TRUE;
+    $edit["options[group_info][group_items][3][value][article]"] = 'article';
+    $edit["options[group_info][group_items][3][value][page]"] = 'page';
     $this->drupalPostForm(NULL, $edit, t('Apply'));
+    $this->assertUrl('admin/structure/views/view/test_exposed_admin_ui/edit/default', array(), 'Correct validation of the node type filter.');
+    $this->assertNoGroupedFilterErrors();
 
-    // Select the empty operator, so the empty value should not trigger a form
-    // error.
+    // Validate an "is empty" filter -- title without value is valid.
     $this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/body_value');
     $edit = array();
-    $edit["options[group_info][group_items][1][title]"] = $this->randomMachineName();
+    $edit["options[group_info][group_items][1][title]"] = 'No body';
     $edit["options[group_info][group_items][1][operator]"] = 'empty';
     $this->drupalPostForm(NULL, $edit, t('Apply'));
-    $this->assertUrl('admin/structure/views/view/test_exposed_admin_ui/edit/default', array(), 'Validation did not run for the empty operator.');
-    // Test the validation error message text is not shown.
-    $this->assertNoText(t('The value is required if title for this item is defined.'));
+    $this->assertUrl('admin/structure/views/view/test_exposed_admin_ui/edit/default', array(), 'The "empty" operator validates correctly.');
+    $this->assertNoGroupedFilterErrors();
 
-    // Validate that all the titles are defined for each group
-    $this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
+    // Ensure the string "0" can be used as a value for numeric filters.
+    $this->drupalPostForm('admin/structure/views/nojs/add-handler/test_exposed_admin_ui/default/filter', array('name[node_field_data.nid]' => TRUE), t('Add and configure @handler', array('@handler' => t('filter criteria'))));
+    $this->drupalPostForm(NULL, array(), t('Expose filter'));
+    $this->drupalPostForm(NULL, array(), t('Grouped filters'));
     $edit = array();
-    $edit["options[group_info][group_items][1][title]"] = 'Is Article';
-    $edit["options[group_info][group_items][1][value][article]"] = TRUE;
+    $edit['options[group_info][group_items][1][title]'] = 'Testing zero';
+    $edit['options[group_info][group_items][1][operator]'] = '>';
+    $edit['options[group_info][group_items][1][value][value]'] = '0';
+    $this->drupalPostForm(NULL, $edit, t('Apply'));
+    $this->assertUrl('admin/structure/views/view/test_exposed_admin_ui/edit/default', array(), 'A string "0" is a valid value.');
+    $this->assertNoGroupedFilterErrors();
 
-    // This should trigger an error
-    $edit["options[group_info][group_items][2][title]"] = '';
-    $edit["options[group_info][group_items][2][value][page]"] = TRUE;
+    // Ensure "between" filters validate correctly.
+    $this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/nid');
+    $edit['options[group_info][group_items][1][title]'] = 'ID between test';
+    $edit['options[group_info][group_items][1][operator]'] = 'between';
+    $edit['options[group_info][group_items][1][value][min]'] = '0';
+    $edit['options[group_info][group_items][1][value][max]'] = '10';
+    $this->drupalPostForm(NULL, $edit, t('Apply'));
+    $this->assertUrl('admin/structure/views/view/test_exposed_admin_ui/edit/default', array(), 'The "between" filter validates correctly.');
+    $this->assertNoGroupedFilterErrors();
+  }
 
-    $edit["options[group_info][group_items][3][title]"] = 'Is Page and Article';
-    $edit["options[group_info][group_items][3][value][article]"] = TRUE;
-    $edit["options[group_info][group_items][3][value][page]"] = TRUE;
+  public function testGroupedFilterAdminUiErrors() {
+    // Select the empty operator without a title specified.
+    $this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/body_value');
+    $edit = array();
+    $edit["options[group_info][group_items][1][title]"] = '';
+    $edit["options[group_info][group_items][1][operator]"] = 'empty';
     $this->drupalPostForm(NULL, $edit, t('Apply'));
-    $this->assertRaw(t('The title is required if value for this item is defined.'), 'Group items should have a title');
+    $this->assertText($this->groupFormUiErrors['missing_title_empty_operator']);
 
-    // Un-expose the filter.
+    // Specify a title without a value.
     $this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
-    $this->drupalPostForm(NULL, array(), t('Hide filter'));
-
-    // After Un-exposing the filter, Operator and Value should be shown again.
-    $this->assertFieldById('edit-options-operator-in', '', 'Operator In exists after hide filter');
-    $this->assertFieldById('edit-options-operator-not-in', '', 'Operator Not In exists after hide filter');
-    $this->assertFieldById('edit-options-value-page', '', 'Checkbox for Page exists after hide filter');
-    $this->assertFieldById('edit-options-value-article', '', 'Checkbox for Article exists after hide filter');
-
-    // Click the Expose sort button.
+    $this->drupalPostForm('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type', [], t('Expose filter'));
+    $this->drupalPostForm('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type', [], t('Grouped filters'));
     $edit = array();
-    $this->drupalPostForm('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/sort/created', $edit, t('Expose sort'));
-    // Check the label of the expose button.
-    $this->helperButtonHasLabel('edit-options-expose-button-button', t('Hide sort'));
-    $this->assertFieldById('edit-options-expose-label', '', 'Make sure a label field is shown');
+    $edit["options[group_info][group_items][1][title]"] = 'Is Article';
+    $this->drupalPostForm(NULL, $edit, t('Apply'));
+    $this->assertText($this->groupFormUiErrors['missing_value']);
 
-    // Test adding a new exposed sort criteria.
-    $view_id = $this->randomView()['id'];
-    $this->drupalGet("admin/structure/views/nojs/add-handler/$view_id/default/sort");
-    $this->drupalPostForm(NULL, ['name[node_field_data.created]' => 1], t('Add and configure @handler', ['@handler' => t('sort criteria')]));
-    $this->assertFieldByXPath('//input[@name="options[order]" and @checked="checked"]', 'ASC', 'The default order is set.');
-    // Change the order and expose the sort.
-    $this->drupalPostForm(NULL, ['options[order]' => 'DESC'], t('Apply'));
-    $this->drupalPostForm("admin/structure/views/nojs/handler/$view_id/default/sort/created", [], t('Expose sort'));
-    $this->assertFieldByXPath('//input[@name="options[order]" and @checked="checked"]', 'DESC');
-    $this->assertFieldByName('options[expose][label]', 'Authored on', 'The default label is set.');
-    // Change the label and save the view.
-    $edit = ['options[expose][label]' => $this->randomString()];
+    // Specify a value without a title.
+    $this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
+    $edit = array();
+    $edit["options[group_info][group_items][1][title]"] = '';
+    $edit["options[group_info][group_items][1][value][article]"] = 'article';
     $this->drupalPostForm(NULL, $edit, t('Apply'));
-    $this->drupalPostForm(NULL, [], t('Save'));
-    // Check that the values were saved.
-    $display = View::load($view_id)->getDisplay('default');
-    $this->assertTrue($display['display_options']['sorts']['created']['exposed']);
-    $this->assertEqual($display['display_options']['sorts']['created']['expose'], ['label' => $edit['options[expose][label]']]);
-    $this->assertEqual($display['display_options']['sorts']['created']['order'], 'DESC');
+    $this->assertText($this->groupFormUiErrors['missing_title']);
+  }
+
+  /**
+   * Asserts that there are no Grouped Filters errors.
+   *
+   * @param string $message
+   *   The assert message.
+   * @param string $group
+   *   The assertion group.
+   *
+   * @return bool
+   *   Result of the assertion.
+   */
+  protected function assertNoGroupedFilterErrors($message = '', $group = 'Other') {
+    foreach ($this->groupFormUiErrors as $error) {
+      $err_message = $message;
+      if (empty($err_message)) {
+        $err_message = "Verify that '$error' is not in the HTML output.";
+      }
+      if (empty($message)) {
+        return $this->assertNoRaw($error, $err_message, $group);
+      }
+    }
+    return TRUE;
   }
 }
