diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 6b68f1d..86f7b4a 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -507,15 +507,25 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
       }
     }
 
-    // We store the resulting output in $elements['#markup'], to be consistent
-    // with how render cached output gets stored. This ensures that placeholder
-    // replacement logic gets the same data to work with, no matter if #cache is
-    // disabled, #cache is enabled, there is a cache hit or miss.
-    $prefix = isset($elements['#prefix']) ? $this->xssFilterAdminIfUnsafe($elements['#prefix']) : '';
-    $suffix = isset($elements['#suffix']) ? $this->xssFilterAdminIfUnsafe($elements['#suffix']) : '';
-
-    $elements['#markup'] = Markup::create($prefix . $elements['#children'] . $suffix);
-
+    // When render elements are being rendered, the caller is responsible of
+    // wrapping the render element with prefix and suffix to ensure that they
+    // are added only once, and that they are added in the final start and
+    // ending.
+
+    // Only apply the prefix and suffix markup around the children if they have
+    // not already been rendered by the theme manager.
+    if (!isset($elements['#render_children'])) {
+      // We store the resulting output in $elements['#markup'], to be consistent
+      // with how render cached output gets stored. This ensures that placeholder
+      // replacement logic gets the same data to work with, no matter if #cache is
+      // disabled, #cache is enabled, there is a cache hit or miss.
+      $prefix = isset($elements['#prefix']) ? $this->xssFilterAdminIfUnsafe($elements['#prefix']) : '';
+      $suffix = isset($elements['#suffix']) ? $this->xssFilterAdminIfUnsafe($elements['#suffix']) : '';
+      $elements['#markup'] = Markup::create($prefix . $elements['#children'] . $suffix);
+    }
+    else {
+      $elements['#markup'] = $elements['#children'];
+    }
     // We've rendered this element (and its subtree!), now update the context.
     $context->update($elements);
 
diff --git a/core/modules/file/src/Tests/FileFieldValidateTest.php b/core/modules/file/src/Tests/FileFieldValidateTest.php
index 12fe925..6fa1d6c 100644
--- a/core/modules/file/src/Tests/FileFieldValidateTest.php
+++ b/core/modules/file/src/Tests/FileFieldValidateTest.php
@@ -190,4 +190,26 @@ public function testFileRemoval() {
     $this->assertText('Article ' . $node->getTitle() . ' has been updated.');
   }
 
+  /**
+   * Test the validation message is displayed only once for ajax uploads.
+   */
+  public function testAJAXValidationMessages() {
+    $type_name = 'article';
+    $field_name = strtolower($this->randomMachineName());
+    $this->createFileField($field_name, 'node', $type_name);
+
+    $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 c5ea831..8800cdc 100644
--- a/core/modules/image/src/Tests/ImageFieldValidateTest.php
+++ b/core/modules/image/src/Tests/ImageFieldValidateTest.php
@@ -106,4 +106,25 @@ function testRequiredAttributes() {
     $this->assertNoText(t('Alternative text field is required.'));
     $this->assertNoText(t('Title field is required.'));
   }
+
+  /**
+   * Test the validation message is displayed only once for ajax uploads.
+   */
+  public function testAJAXValidationMessages() {
+    $field_name = strtolower($this->randomMachineName());
+    $this->createImageField($field_name, 'article', array(), array());
+
+    $this->drupalGet('node/add/article');
+    $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.');
+  }
 }
