diff --git a/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php b/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php
index e16ddba453c03ba50ec3a9c000c90852e581bf93..0d5425009c04d2da136a57dfe34253e7a9bbd650 100644
--- a/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php
+++ b/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php
@@ -278,15 +278,28 @@ public function post(Request $request, $entity_type_id, $bundle, $field_name) {
       $file->setMimeType($this->mimeTypeGuesser->guess($prepared_filename));
       @trigger_error('\Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Implement \Symfony\Component\Mime\MimeTypeGuesserInterface instead. See https://www.drupal.org/node/3133341', E_USER_DEPRECATED);
     }
-    $file->setFileUri($file_uri);
+    $file->setFileUri($temp_file_path);
     // Set the size. This is done in File::preSave() but we validate the file
     // before it is saved.
     $file->setSize(@filesize($temp_file_path));
 
-    // Validate the file entity against entity-level validation and field-level
-    // validators.
-    $this->validate($file, $validators);
+    // Validate the file against field-level validators first while the file is
+    // still a temporary file. Validation is split up in 2 steps to be the same
+    // as in _file_save_upload_single().
+    // For backwards compatibility this part is copied from ::validate() to
+    // leave that method behavior unchanged.
+    // @todo Improve this with a file uploader service in
+    //   https://www.drupal.org/project/drupal/issues/2940383
+    $errors = file_validate($file, $validators);
+
+    if (!empty($errors)) {
+      $message = "Unprocessable Entity: file validation failed.\n";
+      $message .= implode("\n", array_map([PlainTextOutput::class, 'renderFromHtml'], $errors));
+
+      throw new UnprocessableEntityHttpException($message);
+    }
 
+    $file->setFileUri($file_uri);
     // Move the file to the correct location after validation. Use
     // FileSystemInterface::EXISTS_ERROR as the file location has already been
     // determined above in FileSystem::getDestinationFilename().
@@ -297,6 +310,9 @@ public function post(Request $request, $entity_type_id, $bundle, $field_name) {
       throw new HttpException(500, 'Temporary file could not be moved to file location');
     }
 
+    // Second step of the validation on the file object itself now.
+    $this->resourceValidate($file);
+
     $file->save();
 
     $this->lock->release($lock_id);
@@ -449,6 +465,11 @@ protected function validateAndLoadFieldDefinition($entity_type_id, $bundle, $fie
   /**
    * Validates the file.
    *
+   * @todo this method is unused in this class because file validation needs to
+   *   be split up in 2 steps in ::post(). Add a deprecation notice as soon as a
+   *   central core file upload service can be used in this class.
+   *   See https://www.drupal.org/project/drupal/issues/2940383
+   *
    * @param \Drupal\file\FileInterface $file
    *   The file entity to validate.
    * @param array $validators
diff --git a/core/modules/jsonapi/src/Controller/TemporaryJsonapiFileFieldUploader.php b/core/modules/jsonapi/src/Controller/TemporaryJsonapiFileFieldUploader.php
index 6682bb3a6f8ef7430dc4e56e60230f8100d9abba..d07b4562ec84168c794f3cf7d7a27d89949b6aa0 100644
--- a/core/modules/jsonapi/src/Controller/TemporaryJsonapiFileFieldUploader.php
+++ b/core/modules/jsonapi/src/Controller/TemporaryJsonapiFileFieldUploader.php
@@ -19,6 +19,7 @@
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Utility\Token;
 use Drupal\Component\Render\PlainTextOutput;
+use Drupal\Core\Entity\EntityConstraintViolationList;
 use Drupal\file\Entity\File;
 use Drupal\file\Plugin\Field\FieldType\FileFieldItemList;
 use Psr\Log\LoggerInterface;
@@ -199,18 +200,37 @@ public function handleFileUploadForField(FieldDefinitionInterface $field_definit
       @trigger_error('\Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Implement \Symfony\Component\Mime\MimeTypeGuesserInterface instead. See https://www.drupal.org/node/3133341', E_USER_DEPRECATED);
       $file->setMimeType($this->mimeTypeGuesser->guess($prepared_filename));
     }
-    $file->setFileUri($file_uri);
+    $file->setFileUri($temp_file_path);
     // Set the size. This is done in File::preSave() but we validate the file
     // before it is saved.
     $file->setSize(@filesize($temp_file_path));
 
-    // Validate the file entity against entity-level validation and field-level
-    // validators.
-    $violations = $this->validate($file, $validators);
-    if ($violations->count() > 0) {
+    // Validate the file against field-level validators first while the file is
+    // still a temporary file. Validation is split up in 2 steps to be the same
+    // as in _file_save_upload_single().
+    // For backwards compatibility this part is copied from ::validate() to
+    // leave that method behavior unchanged.
+    // @todo Improve this with a file uploader service in
+    //   https://www.drupal.org/project/drupal/issues/2940383
+    $errors = file_validate($file, $validators);
+    if (!empty($errors)) {
+      $violations = new EntityConstraintViolationList($file);
+      $translator = new DrupalTranslator();
+      $entity = EntityAdapter::createFromEntity($file);
+      foreach ($errors as $error) {
+        $violation = new ConstraintViolation($translator->trans($error),
+          $error,
+          [],
+          $entity,
+          '',
+          NULL
+        );
+        $violations->add($violation);
+      }
       return $violations;
     }
 
+    $file->setFileUri($file_uri);
     // Move the file to the correct location after validation. Use
     // FileSystemInterface::EXISTS_ERROR as the file location has already been
     // determined above in FileSystem::getDestinationFilename().
@@ -221,6 +241,16 @@ public function handleFileUploadForField(FieldDefinitionInterface $field_definit
       throw new HttpException(500, 'Temporary file could not be moved to file location');
     }
 
+    // Second step of the validation on the file object itself now.
+    $violations = $file->validate();
+
+    // Remove violations of inaccessible fields as they cannot stem from our
+    // changes.
+    $violations->filterByFieldAccess();
+    if ($violations->count() > 0) {
+      return $violations;
+    }
+
     $file->save();
 
     $this->lock->release($lock_id);
@@ -359,6 +389,11 @@ protected function streamUploadData() {
   /**
    * Validates the file.
    *
+   * @todo this method is unused in this class because file validation needs to
+   *   be split up in 2 steps in ::handleFileUploadForField(). Add a deprecation
+   *   notice as soon as a central core file upload service can be used in this
+   *   class. See https://www.drupal.org/project/drupal/issues/2940383
+   *
    * @param \Drupal\file\FileInterface $file
    *   The file entity to validate.
    * @param array $validators