diff --git a/core/lib/Drupal/Core/Render/Element/MachineName.php b/core/lib/Drupal/Core/Render/Element/MachineName.php
index ace452b..0ccfd6a 100644
--- a/core/lib/Drupal/Core/Render/Element/MachineName.php
+++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
@@ -246,12 +246,37 @@ public static function validateMachineName(&$element, FormStateInterface $form_s
       }
     }
 
-    // Verify that the machine name is unique.
-    if ($element['#default_value'] !== $element['#value']) {
-      $function = $element['#machine_name']['exists'];
-      if (call_user_func($function, $element['#value'], $element, $form_state)) {
-        $form_state->setError($element, t('The machine-readable name is already in use. It must be unique.'));
+    // Verify that the machine name is unique. Check form state if we need to
+    // explicitly validate the element. This happens every time when an error
+    // has been set, since this breaks on AJAX requests.
+    // @see https://www.drupal.org/node/2557299.
+    $needs_revalidation = $form_state->get('needs_revalidation', $element['#name']);
+    if ($needs_revalidation) {
+      static::isUnique($element, $form_state);
+    }
+    elseif ($element['#default_value'] !== $element['#value']) {
+      static::isUnique($element, $form_state);
+    }
+  }
+
+  /**
+   * Verifies that the machine name is unique.
+   *
+   * @param array $element
+   *   The form element
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param (optional) bool $set_needs_revalidation
+   *   Whether to set a property on form state in case of an error to revalidate
+   *   the element again on the next submission.
+   */
+  public static function isUnique($element, FormStateInterface $form_state, $set_needs_revalidation = TRUE) {
+    $function = $element['#machine_name']['exists'];
+    if (call_user_func($function, $element['#value'], $element, $form_state)) {
+      if ($set_needs_revalidation) {
+        $form_state->set('needs_revalidation', $element['#name']);
       }
+      $form_state->setError($element, t('The machine-readable name is already in use. It must be unique.'));
     }
   }
 
diff --git a/core/modules/ckeditor/tests/src/Functional/CKEditorAdminTest.php b/core/modules/ckeditor/tests/src/Functional/CKEditorAdminTest.php
index ce3411c..7ed1198 100644
--- a/core/modules/ckeditor/tests/src/Functional/CKEditorAdminTest.php
+++ b/core/modules/ckeditor/tests/src/Functional/CKEditorAdminTest.php
@@ -216,6 +216,20 @@ public function testExistingFormat() {
     $editor = Editor::load('filtered_html');
     $this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
     $this->assertEqual($expected_settings, $editor->getSettings());
+
+    $this->drupalGet('admin/config/content/formats/add');
+    // Now attempt to add another filter format with the same editor and same
+    // machine name.
+    $edit = array(
+      'format' => 'filtered_html',
+      'name' => 'Filtered HTML',
+      'editor[editor]' => 'ckeditor',
+    );
+    $this->drupalPostAjaxForm(NULL, $edit, 'editor_configure');
+    $this->assertResponse(200);
+    $this->drupalPostForm(NULL, $edit, t('Save configuration'));
+    $this->assertResponse(200);
+    $this->assertText(t('The machine-readable name is already in use. It must be unique.'));
   }
 
   /**
diff --git a/core/modules/system/src/Tests/Form/FormTest.php b/core/modules/system/src/Tests/Form/FormTest.php
index 4b8d042..776c954 100644
--- a/core/modules/system/src/Tests/Form/FormTest.php
+++ b/core/modules/system/src/Tests/Form/FormTest.php
@@ -746,4 +746,37 @@ public function testRequiredAttribute() {
     $this->assertTrue(!empty($element), 'The textarea has the proper required attribute.');
   }
 
+  /**
+   * Test machine name is still required after an ajax submit.
+   *
+   * This protects against regression for https://www.drupal.org/node/2557299.
+   */
+  public function testMachineNameRequiredFormAjaxSubmit() {
+    $this->drupalGet('/form-test/form-test-machine-name-validation');
+
+    $edit = [];
+    $this->drupalPostForm(NULL, $edit, 'Save');
+    $this->assertResponse(200);
+    $this->assertText('Machine-readable name field is required.');
+
+    $edit = [
+      'name' => 'test 1',
+      'id' => 'machine1',
+      'snack' => 'apple'
+    ];
+    $this->drupalPostForm(NULL, $edit, 'Save');
+    $this->assertResponse(200);
+    $this->assertText('The form_test_machine_name_validation_form form has been submitted successfully.');
+
+    $edit = [
+      'name' => 'test 2',
+      'id' => 'duplicate',
+      'snack' => 'potato',
+    ];
+    $this->drupalPostAjaxForm(NULL, $edit, 'snack_configure');
+    $this->drupalPostForm(NULL, $edit, 'Save');
+    $this->assertResponse(200);
+    $this->assertText('The machine-readable name is already in use. It must be unique.');
+  }
+
 }
diff --git a/core/modules/system/tests/modules/form_test/form_test.routing.yml b/core/modules/system/tests/modules/form_test/form_test.routing.yml
index 2250a0b..bf6bdd3 100644
--- a/core/modules/system/tests/modules/form_test/form_test.routing.yml
+++ b/core/modules/system/tests/modules/form_test/form_test.routing.yml
@@ -489,3 +489,11 @@ form_test.get_form:
     _form: '\Drupal\form_test\Form\FormTestGetForm'
   requirements:
     _access: 'TRUE'
+
+form_test.machine_name_validation:
+  path: '/form-test/form-test-machine-name-validation'
+  defaults:
+    _form: '\Drupal\form_test\Form\FormTestMachineNameValidationForm'
+    _title: 'Form machine name validation test'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestMachineNameValidationForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestMachineNameValidationForm.php
new file mode 100644
index 0000000..40aed30
--- /dev/null
+++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestMachineNameValidationForm.php
@@ -0,0 +1,153 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\form_test\Form\FormTestMachineNameValidationForm.
+ */
+
+namespace Drupal\form_test\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Form to test whether machine name validation works with ajax requests.
+ */
+class FormTestMachineNameValidationForm extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'form_test_machine_name_validation_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+
+    $form['name'] = [
+      '#type' => 'textfield',
+      '#default_value' => $form_state->getValue('name'),
+      '#maxlength' => 50,
+      '#required' => TRUE,
+      '#title' => 'Name'
+    ];
+
+    // The default value simulates how an entity form works, which has default
+    // values based on an entity, which is updated in an afterBuild callback.
+    // During validation and after build, limit_validation_errors is not
+    // in effect, which means that getValue('id') does return a value, while it
+    // does not during the submit callback. Therefore, this test sets the value
+    // in ::buildAjaxSnackConfigureFormValidate() and then uses that as the
+    // default value, so that the default value and the value are identical.
+    $form['id'] = [
+      '#type' => 'machine_name',
+      '#default_value' => $form_state->get('id'),
+      '#maxlength' => 50,
+      '#required' => TRUE,
+      '#machine_name' => [
+        'exists' => [$this, 'load'],
+        'source' => ['name'],
+      ],
+    ];
+
+    $form['snack'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Select a snack'),
+      '#options' => [
+        'apple' => 'apple',
+        'pear' => 'pear',
+        'potato' => 'potato',
+      ],
+      '#required' => TRUE,
+      '#ajax' => [
+        'trigger_as' => ['name' => 'snack_configure'],
+        'callback' => '::buildAjaxSnackConfigureForm',
+        'wrapper' => 'snack-config-form',
+        'method' => 'replace',
+        'effect' => 'fade',
+      ],
+    ];
+    $form['snack_configs'] = [
+      '#type' => 'container',
+      '#attributes' => [
+        'id' => 'snack-config-form',
+      ],
+      '#tree' => TRUE,
+    ];
+    $form['snack_configure_button'] = [
+      '#type' => 'submit',
+      '#name' => 'snack_configure',
+      '#value' => 'Configure snack',
+      '#limit_validation_errors' => [['snack']],
+      '#validate' => ['::buildAjaxSnackConfigureFormValidate'],
+      '#submit' => ['::buildAjaxSnackConfigureFormSubmit'],
+      '#executes_submit_callback' => TRUE,
+      '#ajax' => [
+        'callback' => '::buildAjaxSnackConfigureForm',
+        'wrapper' => 'snack-config-form',
+      ],
+      '#attributes' => ['class' => ['js-hide']],
+    ];
+    $form['snack_configs']['apple']['#type'] = 'details';
+    $form['snack_configs']['apple']['#title'] = 'Configure Apple';
+    $form['snack_configs']['apple']['#open'] = TRUE;
+    $form['snack_configs']['pear']['#type'] = 'details';
+    $form['snack_configs']['pear']['#title'] = 'Configure pear';
+    $form['snack_configs']['pear']['#open'] = TRUE;
+    $form['snack_configs']['potato']['#type'] = 'details';
+    $form['snack_configs']['potato']['#title'] = 'Configure potato';
+    $form['snack_configs']['potato']['#open'] = TRUE;
+
+    $form['submit'] = [
+      '#type' => 'submit',
+      '#value' => 'Save',
+    ];
+    return $form;
+  }
+
+  /**
+   * Validate callback that forces a form rebuild.
+   */
+  public function buildAjaxSnackConfigureFormValidate(array $form, FormStateInterface $form_state) {
+    $form_state->set('id', $form_state->getValue('id'));
+  }
+
+  /**
+   * Submit callback that forces a form rebuild.
+   */
+  public function buildAjaxSnackConfigureFormSubmit(array $form, FormStateInterface $form_state) {
+    $form_state->setRebuild();
+  }
+
+  /**
+   * Handles changes to the selected snack configuration.
+   */
+  public function buildAjaxSnackConfigureForm(array $form, FormStateInterface $form_state) {
+    return $form['snack_configs'];
+  }
+
+  /**
+   * Loading stub for machine name
+   *
+   * @param $machine_name
+   * @return bool
+   */
+  public function load($machine_name) {
+    if ($machine_name === 'duplicate') {
+      return TRUE;
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    drupal_set_message('The form_test_machine_name_validation_form form has been submitted successfully.');
+  }
+
+}
