diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index c0003b350a..23e49bc36f 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -928,6 +928,9 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL
     $values['filemime'] = \Drupal::service('file.mime_type.guesser')->guess($values['filename']);
     $file = File::create($values);
 
+    // Ensure that the file object is validated.
+    $file->setValidationRequired(TRUE);
+
     $extensions = '';
     if (isset($validators['file_validate_extensions'])) {
       if (isset($validators['file_validate_extensions'][0])) {
@@ -1046,6 +1049,32 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL
       }
     }
 
+    // We can now validate the file object itself before it's saved.
+    $violations = $file->validate();
+    if (count($violations) > 0) {
+      foreach ($violations as $violation) {
+        $errors[] = $violation->getMessage();
+      }
+    }
+    if (!empty($errors)) {
+      $message = [
+          'error' => [
+              '#markup' => t('The specified file %name could not be uploaded.', ['%name' => $file->getFilename()]),
+          ],
+          'item_list' => [
+              '#theme' => 'item_list',
+              '#items' => $errors,
+          ],
+      ];
+      // @todo Add support for render arrays in drupal_set_message()? See
+      //  https://www.drupal.org/node/2505497.
+      $rendered_message = \Drupal::service('renderer')->renderPlain($message);
+      drupal_set_message($rendered_message, 'error');
+      \Drupal::logger('file')->error($rendered_message);
+      $files[$i] = FALSE;
+      continue;
+    }
+
     // If we made it this far it's safe to record this file in the database.
     $file->save();
     $files[$i] = $file;
diff --git a/core/modules/file/tests/src/Functional/SaveUploadTest.php b/core/modules/file/tests/src/Functional/SaveUploadTest.php
index bebf47e076..281cdeaf17 100644
--- a/core/modules/file/tests/src/Functional/SaveUploadTest.php
+++ b/core/modules/file/tests/src/Functional/SaveUploadTest.php
@@ -127,6 +127,37 @@ public function testNormal() {
     $this->assertTrue(is_file('temporary://' . $dir . '/' . trim(drupal_basename($image3_realpath))));
   }
 
+  /**
+   * Test uploading a duplicate file.
+   */
+  public function testDuplicate() {
+    // It should not be possible to create two managed files with the same URI.
+    $image1 = current($this->drupalGetTestFiles('image'));
+    $edit = ['files[file_test_upload]' => \Drupal::service('file_system')->realpath($image1->uri)];
+    $this->drupalPostForm('file-test/upload', $edit, t('Submit'));
+    $max_fid_after = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField();
+    $file1 = File::load($max_fid_after);
+
+    // Simulate a race condition where two files are uploaded at almost the same
+    // time, by removing the first uploaded file from disk (leaving the entry in
+    // the file_managed table) before trying to upload another file with the
+    // same name.
+    unlink(\Drupal::service('file_system')->realpath($file1->getFileUri()));
+
+    $image2 = $image1;
+    $edit = ['files[file_test_upload]' => \Drupal::service('file_system')->realpath($image2->uri)];
+    $this->drupalPostForm('file-test/upload', $edit, t('Submit'));
+    $this->assertResponse(200, 'Received a 200 response for posted test file.');
+    $message = t('The file %file already exists. Enter a unique file URI.', ['%file' => $file1->getFileUri()]);
+    $this->assertRaw($message, 'Cannot upload a duplicate file.');
+    $max_fid_before_duplicate = $max_fid_after;
+    $max_fid_after = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField();
+    $this->assertEqual($max_fid_before_duplicate, $max_fid_after, 'A new managed file was not created.');
+  }
+
+}
+
+
   /**
    * Test extension handling.
    */
