diff --git a/core/lib/Drupal/Core/Entity/ContentEntityForm.php b/core/lib/Drupal/Core/Entity/ContentEntityForm.php
index 35834ab..c892401 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityForm.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityForm.php
@@ -50,6 +50,11 @@ public static function create(ContainerInterface $container) {
    */
   public function form(array $form, FormStateInterface $form_state) {
     $form = parent::form($form, $form_state);
+
+    // Content entity forms are doing their own entity building so there's no
+    // need to use the parent one.
+    unset($form['#after_build']);
+
     $this->getFormDisplay($form_state)->buildForm($this->entity, $form, $form_state);
     // Allow modules to act before and after form language is updated.
     $form['#entity_builders']['update_form_langcode'] = [$this, 'updateFormLangcode'];
@@ -80,6 +85,17 @@ public function validate(array $form, FormStateInterface $form_state) {
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    parent::submitForm($form, $form_state);
+
+    // Remove button and internal Form API values from submitted values.
+    $form_state->cleanValues();
+    $this->entity = $this->buildEntity($form, $form_state);
+  }
+
+  /**
    * Initializes the form state and the entity before the first form build.
    *
    * @param \Drupal\Core\Form\FormStateInterface $form_state
diff --git a/core/lib/Drupal/Core/Entity/EntityForm.php b/core/lib/Drupal/Core/Entity/EntityForm.php
index a0b18fa..a04ec88 100644
--- a/core/lib/Drupal/Core/Entity/EntityForm.php
+++ b/core/lib/Drupal/Core/Entity/EntityForm.php
@@ -131,13 +131,13 @@ protected function init(FormStateInterface $form_state) {
   /**
    * Returns the actual form array to be built.
    *
-   * @see \Drupal\Core\Entity\EntityForm::build()
+   * @see \Drupal\Core\Entity\EntityForm::processForm()
+   * @see \Drupal\Core\Entity\EntityForm::afterBuild()
    */
   public function form(array $form, FormStateInterface $form_state) {
-    $entity = $this->entity;
-
-    // Add a process callback.
+    // Add process and after_build callbacks.
     $form['#process'][] = '::processForm';
+    $form['#after_build'][] = '::afterBuild';
 
     return $form;
   }
@@ -156,6 +156,30 @@ public function processForm($element, FormStateInterface $form_state, $form) {
   }
 
   /**
+   * Form element #after_build callback: Updates the entity with submitted data.
+   *
+   * This is the default entity object builder function. It is called before any
+   * other AJAX, validate or submit handlers to build the new entity object to
+   * be used by the following AJAX, validate or submit handlers. At this point
+   * of the form workflow, the entity is not yet validated so we have to use a
+   * cloned form state object for updating the entity, thus allowing the initial
+   * form state to be updated. This way, the subsequently invoked handlers can
+   * retrieve a regular entity object to act on.
+   */
+  public function afterBuild(array $element, FormStateInterface $form_state) {
+    // If the form is processing user input, rebuild the entity with the current
+    // form values.
+    if ($form_state->isProcessingInput()) {
+      // Remove button and internal Form API values from submitted values.
+      $clean_form_state = clone $form_state;
+      $clean_form_state->cleanValues();
+      $this->entity = $this->buildEntity($element, $clean_form_state);
+    }
+
+    return $element;
+  }
+
+  /**
    * Returns the action form element for the current entity form.
    */
   protected function actionsElement(array $form, FormStateInterface $form_state) {
@@ -239,25 +263,10 @@ public function validate(array $form, FormStateInterface $form_state) {
 
   /**
    * {@inheritdoc}
-   *
-   * This is the default entity object builder function. It is called before any
-   * other submit handler to build the new entity object to be used by the
-   * following submit handlers. At this point of the form workflow the entity is
-   * validated and the form state can be updated, this way the subsequently
-   * invoked handlers can retrieve a regular entity object to act on. Generally
-   * this method should not be overridden unless the entity requires the same
-   * preparation for two actions, see \Drupal\comment\CommentForm for an example
-   * with the save and preview actions.
-   *
-   * @param array $form
-   *   An associative array containing the structure of the form.
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The current state of the form.
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    // Remove button and internal Form API values from submitted values.
-    $form_state->cleanValues();
-    $this->entity = $this->buildEntity($form, $form_state);
+    // Nothing to do here. An updated entity object is already prepared by
+    // static::afterBuild().
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index 29f4cbf..d29bd8c 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -801,8 +801,8 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
     // The #after_build flag allows any piece of a form to be altered
     // after normal input parsing has been completed.
     if (isset($element['#after_build']) && !isset($element['#after_build_done'])) {
-      foreach ($element['#after_build'] as $callable) {
-        $element = call_user_func_array($callable, array($element, &$form_state));
+      foreach ($element['#after_build'] as $callback) {
+        $element = call_user_func_array($form_state->prepareCallback($callback), array($element, &$form_state));
       }
       $element['#after_build_done'] = TRUE;
     }
diff --git a/core/modules/config/src/Tests/ConfigEntityTest.php b/core/modules/config/src/Tests/ConfigEntityTest.php
index 7cf16e6..1de0264 100644
--- a/core/modules/config/src/Tests/ConfigEntityTest.php
+++ b/core/modules/config/src/Tests/ConfigEntityTest.php
@@ -319,6 +319,44 @@ function testCRUDUI() {
     $this->drupalPostForm('admin/structure/config_test/manage/0/delete', array(), 'Delete');
     $this->assertFalse(entity_load('config_test', '0'), 'Test entity deleted');
 
+    // Create a configuration entity with a property that uses AJAX to show
+    // extra form elements.
+    $this->drupalGet('admin/structure/config_test/add');
+
+    // Test that the dependent element is not shown initially.
+    $this->assertFieldByName('size');
+    $this->assertNoFieldByName('size_value');
+
+    $id = strtolower($this->randomMachineName());
+    $edit = array(
+      'id' => $id,
+      'label' => $this->randomString(),
+      'size' => 'custom',
+    );
+    $this->drupalPostAjaxForm(NULL, $edit, 'size');
+
+    // Check that the dependent element is shown after selecting a 'size' value.
+    $this->assertFieldByName('size');
+    $this->assertFieldByName('size_value');
+
+    // Test the same scenario but it in a non-js case by using a js-hidden
+    // submit button.
+    $this->drupalGet('admin/structure/config_test/add');
+    $this->assertFieldByName('size');
+    $this->assertNoFieldByName('size_value');
+
+    $this->drupalPostForm(NULL, $edit, 'Change size');
+    $this->assertFieldByName('size');
+    $this->assertFieldByName('size_value');
+
+    // Submit the form with the regular 'Save' button and check that the entity
+    // values are correct.
+    $edit += array('size_value' => 'medium');
+    $this->drupalPostForm(NULL, $edit, 'Save');
+
+    $entity = entity_load('config_test', $id);
+    $this->assertEqual($entity->get('size'), 'custom');
+    $this->assertEqual($entity->get('size_value'), 'medium');
   }
 
 }
diff --git a/core/modules/config/src/Tests/ConfigImportUITest.php b/core/modules/config/src/Tests/ConfigImportUITest.php
index 9bc30e3..4f3aea5 100644
--- a/core/modules/config/src/Tests/ConfigImportUITest.php
+++ b/core/modules/config/src/Tests/ConfigImportUITest.php
@@ -68,6 +68,8 @@ function testImport() {
       'label' => 'New',
       'weight' => 0,
       'style' => '',
+      'size' => '',
+      'size_value' => '',
       'protected_property' => '',
     );
     $staging->write($dynamic_name, $original_dynamic_data);
@@ -373,7 +375,9 @@ function testImportErrorLog() {
       'label' => 'Primary',
       'weight' => 0,
       'style' => NULL,
-      'protected_property' => null,
+      'size' => NULL,
+      'size_value' => NULL,
+      'protected_property' => NULL,
     );
     $staging->write($name_primary, $values_primary);
     $values_secondary = array(
@@ -388,7 +392,9 @@ function testImportErrorLog() {
       'label' => 'Secondary Sync',
       'weight' => 0,
       'style' => NULL,
-      'protected_property' => null,
+      'size' => NULL,
+      'size_value' => NULL,
+      'protected_property' => NULL,
     );
     $staging->write($name_secondary, $values_secondary);
     // Verify that there are configuration differences to import.
diff --git a/core/modules/config/src/Tests/ConfigImporterTest.php b/core/modules/config/src/Tests/ConfigImporterTest.php
index 581b5c4..a6b2312 100644
--- a/core/modules/config/src/Tests/ConfigImporterTest.php
+++ b/core/modules/config/src/Tests/ConfigImporterTest.php
@@ -173,6 +173,8 @@ function testNew() {
       'label' => 'New',
       'weight' => 0,
       'style' => '',
+      'size' => '',
+      'size_value' => '',
       'protected_property' => '',
     );
     $staging->write($dynamic_name, $original_dynamic_data);
diff --git a/core/modules/config/tests/config_test/config/schema/config_test.schema.yml b/core/modules/config/tests/config_test/config/schema/config_test.schema.yml
index 9240d4b..f68acde 100644
--- a/core/modules/config/tests/config_test/config/schema/config_test.schema.yml
+++ b/core/modules/config/tests/config_test/config/schema/config_test.schema.yml
@@ -15,6 +15,12 @@ config_test_dynamic:
     style:
       type: string
       label: 'style'
+    size:
+      type: string
+      label: 'Size'
+    size_value:
+      type: string
+      label: 'Size value'
     protected_property:
       type: string
       label: 'Protected property'
diff --git a/core/modules/config/tests/config_test/src/ConfigTestForm.php b/core/modules/config/tests/config_test/src/ConfigTestForm.php
index e77ddb0..975e27b 100644
--- a/core/modules/config/tests/config_test/src/ConfigTestForm.php
+++ b/core/modules/config/tests/config_test/src/ConfigTestForm.php
@@ -54,6 +54,50 @@ public function form(array $form, FormStateInterface $form_state) {
       $form['style']['#options'] = image_style_options();
     }
 
+    // The main premise of entity forms is that we get to work with an entity
+    // object at all times instead of checking submitted values from the form
+    // state.
+    $size = $entity->get('size');
+
+    $form['size_wrapper'] = array(
+      '#type' => 'container',
+      '#attributes' => array(
+        'id' => 'size-wrapper',
+      ),
+    );
+    $form['size_wrapper']['size'] = array(
+      '#type' => 'select',
+      '#title' => 'Size',
+      '#options' => array(
+        'custom' => 'Custom',
+      ),
+      '#empty_option' => '- None -',
+      '#default_value' => $size,
+      '#ajax' => array(
+        'callback' => '::updateSize',
+        'wrapper' => 'size-wrapper',
+      ),
+    );
+    $form['size_wrapper']['size_submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Change size'),
+      '#attributes' => array(
+        'class' => array('js-hide'),
+      ),
+      '#submit' => array(array(get_class($this), 'changeSize')),
+    );
+    $form['size_wrapper']['size_value'] = array(
+      '#type' => 'select',
+      '#title' => 'Custom size value',
+      '#options' => array(
+        'small' => 'Small',
+        'medium' => 'Medium',
+        'large' => 'Large',
+      ),
+      '#default_value' => $entity->get('size_value'),
+      '#access' => !empty($size),
+    );
+
     $form['actions'] = array('#type' => 'actions');
     $form['actions']['submit'] = array(
       '#type' => 'submit',
@@ -68,6 +112,20 @@ public function form(array $form, FormStateInterface $form_state) {
   }
 
   /**
+   * Ajax callback for the size selection element.
+   */
+  public static function updateSize(array $form, FormStateInterface $form_state) {
+    return $form['size_wrapper'];
+  }
+
+  /**
+   * Element submit handler for non-js testing.
+   */
+  public static function changeSize(array $form, FormStateInterface $form_state) {
+    $form_state->setRebuild();
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function save(array $form, FormStateInterface $form_state) {
