diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 0bf1d63..2ff7c8b 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -209,6 +209,19 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
       return '';
     }
 
+    // Render only the children if the #render_children property is set.
+    if (isset($elements['#render_children'])) {
+      $children = Element::children($elements);
+
+      if (empty($children)) {
+        return '';
+      }
+
+      // It is okay to modify the original elements for this, because this will
+      // be called from a twig template, where the variable is passed by value.
+      $elements = array_intersect_key($elements, array_flip($children));
+    }
+
     if (!isset($elements['#access']) && isset($elements['#access_callback'])) {
       if (is_string($elements['#access_callback']) && strpos($elements['#access_callback'], '::') === FALSE) {
         $elements['#access_callback'] = $this->controllerResolver->getControllerFromDefinition($elements['#access_callback']);
@@ -428,10 +441,8 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
     }
 
     // Call the element's #theme function if it is set. Then any children of the
-    // element have to be rendered there. If the internal #render_children
-    // property is set, do not call the #theme function to prevent infinite
-    // recursion.
-    if ($theme_is_implemented && !isset($elements['#render_children'])) {
+    // element have to be rendered there.
+    if ($theme_is_implemented) {
       $elements['#children'] = $this->theme->render($elements['#theme'], $elements);
 
       // If ThemeManagerInterface::render() returns FALSE this means that the
@@ -440,10 +451,10 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
       $theme_is_implemented = ($elements['#children'] !== FALSE);
     }
 
-    // If #theme is not implemented or #render_children is set and the element
-    // has an empty #children attribute, render the children now. This is the
-    // same process as Renderer::render() but is inlined for speed.
-    if ((!$theme_is_implemented || isset($elements['#render_children'])) && empty($elements['#children'])) {
+    // If #theme is not implemented and the element has an empty #children
+    // attribute, render the children now. This is the same process as
+    // Renderer::render() but is inlined for speed.
+    if (!$theme_is_implemented && empty($elements['#children'])) {
       foreach ($children as $key) {
         $elements['#children'] .= $this->doRender($elements[$key]);
       }
@@ -467,9 +478,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
     // because the #type 'page' render array from drupal_prepare_page() would
     // render the $page and wrap it into the html.html.twig template without the
     // attached assets otherwise.
-    // If the internal #render_children property is set, do not call the
-    // #theme_wrappers function(s) to prevent infinite recursion.
-    if (isset($elements['#theme_wrappers']) && !isset($elements['#render_children'])) {
+    if (isset($elements['#theme_wrappers'])) {
       foreach ($elements['#theme_wrappers'] as $key => $value) {
         // If the value of a #theme_wrappers item is an array then the theme
         // hook is found in the key of the item and the value contains attribute
diff --git a/core/modules/file/src/Tests/FileFieldValidateTest.php b/core/modules/file/src/Tests/FileFieldValidateTest.php
index 9d43060..de3b1c5 100644
--- a/core/modules/file/src/Tests/FileFieldValidateTest.php
+++ b/core/modules/file/src/Tests/FileFieldValidateTest.php
@@ -71,8 +71,10 @@ function testFileMaxSize() {
     $field_name = strtolower($this->randomMachineName());
     $this->createFileField($field_name, 'node', $type_name, array(), array('required' => '1'));
 
-    $small_file = $this->getTestFile('text', 131072); // 128KB.
-    $large_file = $this->getTestFile('text', 1310720); // 1.2MB
+    // 128KB.
+    $small_file = $this->getTestFile('text', 131072);
+    // 1.2MB.
+    $large_file = $this->getTestFile('text', 1310720);
 
     // Test uploading both a large and small file with different increments.
     $sizes = array(
@@ -185,4 +187,25 @@ public function testFileRemoval() {
     $this->assertText('Article ' . $node->getTitle() . ' has been updated.');
   }
 
+  /**
+   * Test the validation message is displayed only once for ajax uploads.
+   */
+  public function testAJAXValidationMessage() {
+    $field_name = strtolower($this->randomMachineName());
+    $this->createFileField($field_name, 'node', 'article');
+
+    $this->drupalGet('node/add/article');
+    /** @var \Drupal\file\FileInterface $image_file */
+    $image_file = $this->getTestFile('image');
+    $edit = array(
+      'files[' . $field_name . '_0]' => $this->container->get('file_system')->realpath($image_file->getFileUri()),
+      'title[0][value]' => $this->randomMachineName(),
+    );
+    $this->drupalPostAjaxForm(NULL, $edit, $field_name . '_0_upload_button');
+    $elements = $this->xpath('//div[contains(@class, :class)]', array(
+      ':class' => 'messages--error',
+    ));
+    $this->assertEqual(count($elements), 1, 'Ajax validation messages are displayed once.');
+  }
+
 }
diff --git a/core/modules/image/src/Tests/ImageFieldValidateTest.php b/core/modules/image/src/Tests/ImageFieldValidateTest.php
index 2532524..c873f56 100644
--- a/core/modules/image/src/Tests/ImageFieldValidateTest.php
+++ b/core/modules/image/src/Tests/ImageFieldValidateTest.php
@@ -8,6 +8,7 @@
  * @group image
  */
 class ImageFieldValidateTest extends ImageFieldTestBase {
+
   /**
    * Test min/max resolution settings.
    */
@@ -102,4 +103,44 @@ function testRequiredAttributes() {
     $this->assertNoText(t('Title field is required.'));
   }
 
+  /**
+   * Returns field settings.
+   *
+   * @param int[] $min_resolution
+   *   The minimum width and height resolution setting.
+   * @param int[] $max_resolution
+   *   The maximum width and height resolution setting.
+   *
+   * @return array
+   */
+  protected function getFieldSettings($min_resolution, $max_resolution) {
+    return [
+      'max_resolution' => $max_resolution['width'] . 'x' . $max_resolution['height'],
+      'min_resolution' => $min_resolution['width'] . 'x' . $min_resolution['height'],
+      'alt_field' => 0,
+    ];
+  }
+
+  /**
+   * Test the validation message is displayed only once for ajax uploads.
+   */
+  public function testAJAXValidationMessage() {
+    $field_name = strtolower($this->randomMachineName());
+    $this->createImageField($field_name, 'article');
+
+    $this->drupalGet('node/add/article');
+    /** @var \Drupal\file\FileInterface[] $text_files */
+    $text_files = $this->drupalGetTestFiles('text');
+    $text_file = reset($text_files);
+    $edit = array(
+      'files[' . $field_name . '_0]' => $this->container->get('file_system')->realpath($text_file->uri),
+      'title[0][value]' => $this->randomMachineName(),
+    );
+    $this->drupalPostAjaxForm(NULL, $edit, $field_name . '_0_upload_button');
+    $elements = $this->xpath('//div[contains(@class, :class)]', array(
+      ':class' => 'messages--error',
+    ));
+    $this->assertEqual(count($elements), 1, 'Ajax validation messages are displayed once.');
+  }
+
 }
