diff --git a/core/lib/Drupal/Core/Entity/Exception/UnknownFieldException.php b/core/lib/Drupal/Core/Entity/Exception/UnknownFieldException.php
new file mode 100644
index 0000000000..90eeef71b0
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Exception/UnknownFieldException.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Drupal\Core\Entity\Exception;
+
+/**
+ * Exception thrown if a requested field does not exist.
+ */
+class UnknownFieldException extends \RuntimeException {}
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index f17b773d8c..33307c1e3f 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -27,6 +27,7 @@
 use Drupal\file\Entity\File;
 use Drupal\file\FileInterface;
 use Drupal\file\Upload\FileValidationException;
+use Drupal\file\Upload\FormUploadedFile;
 use Symfony\Component\HttpFoundation\File\Exception\FileException as SymfonyFileException;
 use Symfony\Component\HttpFoundation\File\Exception\FormSizeFileException;
 use Symfony\Component\HttpFoundation\File\Exception\IniSizeFileException;
@@ -913,7 +914,8 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL
   /** @var \Symfony\Component\HttpFoundation\File\UploadedFile $uploaded_file */
   foreach ($uploaded_files as $i => $uploaded_file) {
     try {
-      $result = $file_upload_handler->handleFileUpload($uploaded_file, $validators, $destination, $replace);
+      $form_uploaded_file = new FormUploadedFile($uploaded_file);
+      $result = $file_upload_handler->handleFileUpload($form_uploaded_file, $validators, $destination, $replace);
       $file = $result->getFile();
       // If the filename has been modified, let the user know.
       if ($result->isRenamed()) {
diff --git a/core/modules/file/file.services.yml b/core/modules/file/file.services.yml
index 5558d383d3..080603e5be 100644
--- a/core/modules/file/file.services.yml
+++ b/core/modules/file/file.services.yml
@@ -4,6 +4,24 @@ services:
     arguments: ['@config.factory', '@database', 'file_usage']
     tags:
       - { name: backend_overridable }
+
   file.upload_handler:
     class: Drupal\file\Upload\FileUploadHandler
     arguments: [ '@file_system', '@entity_type.manager', '@stream_wrapper_manager', '@event_dispatcher', '@file.mime_type.guesser', '@current_user', '@request_stack' ]
+
+  file.field_definition_resolver:
+    class: Drupal\file\FileFieldDefinitionResolver
+    arguments: [ '@entity_field.manager' ]
+
+  file.field_upload_handler:
+    class: Drupal\file\Upload\FileFieldUploadHandler
+    arguments: [ '@file_system', '@entity_type.manager', '@stream_wrapper_manager', '@event_dispatcher', '@file.mime_type.guesser', '@current_user', '@request_stack', '@token' ]
+  file.raw_file_uploader:
+    class: Drupal\file\Upload\RawFileUploader
+    arguments: [ '@file_system' ]
+  file.upload_filename_extractor:
+    class: Drupal\file\Upload\FilenameExtractor
+    arguments: [ '@file_system' ]
+  file.upload_access_checker:
+    class: Drupal\file\Upload\FileFieldUploadAccessChecker
+    arguments: [ '@entity_type.manager', '@current_user' ]
diff --git a/core/modules/file/src/FileFieldDefinitionResolver.php b/core/modules/file/src/FileFieldDefinitionResolver.php
new file mode 100644
index 0000000000..1c2a34283d
--- /dev/null
+++ b/core/modules/file/src/FileFieldDefinitionResolver.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Drupal\file;
+
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\Exception\UnknownFieldException;
+use Drupal\Core\Field\FieldDefinitionInterface;
+
+/**
+ * Provides a File field resolver.
+ */
+class FileFieldDefinitionResolver {
+
+  /**
+   * The entity field manager.
+   *
+   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+   */
+  protected $entityFieldManager;
+
+  /**
+   * FileFieldResolver constructor.
+   *
+   *   The entity type manager.
+   *
+   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entityFieldManager
+   *   The entity field manager.
+   */
+  public function __construct(EntityFieldManagerInterface $entityFieldManager) {
+    $this->entityFieldManager = $entityFieldManager;
+  }
+
+  /**
+   * Validates and loads a field definition instance.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID the field is attached to.
+   * @param string $bundle
+   *   The bundle the field is attached to.
+   * @param string $field_name
+   *   The field name.
+   *
+   * @return \Drupal\Core\Field\FieldDefinitionInterface
+   *   The field definition.
+   *
+   * @throws \Drupal\Core\Entity\Exception\UnknownFieldException
+   *   Thrown when the field does not exist.
+   * @throws \InvalidArgumentException
+   *   Thrown when the target type of the field is not a file.
+   */
+  public function resolveFieldDefinition(string $entity_type_id, string $bundle, string $field_name): FieldDefinitionInterface {
+    $field_definitions = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle);
+    if (!isset($field_definitions[$field_name])) {
+      throw new UnknownFieldException(sprintf('Field "%s" does not exist.', $field_name));
+    }
+
+    $field_definition = $field_definitions[$field_name];
+    if ($field_definition->getSetting('target_type') !== 'file') {
+      throw new \InvalidArgumentException(sprintf('"%s" is not a file field', $field_name));
+    }
+    return $field_definition;
+  }
+
+}
diff --git a/core/modules/file/src/Upload/FileFieldUploadAccessChecker.php b/core/modules/file/src/Upload/FileFieldUploadAccessChecker.php
new file mode 100644
index 0000000000..4b9b0d3ae6
--- /dev/null
+++ b/core/modules/file/src/Upload/FileFieldUploadAccessChecker.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace Drupal\file\Upload;
+
+use Drupal\Core\Access\AccessResultInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Provides an access checker for file field uploads.
+ */
+class FileFieldUploadAccessChecker {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $currentUser;
+
+  /**
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
+   * @param \Drupal\Core\Session\AccountInterface $currentUser
+   */
+  public function __construct(EntityTypeManagerInterface $entityTypeManager, AccountInterface $currentUser) {
+    $this->entityTypeManager = $entityTypeManager;
+    $this->currentUser = $currentUser;
+  }
+
+  /**
+   * Checks if the current user has access to upload the file.
+   *
+   * @param \Drupal\Core\Field\FieldDefinitionInterface $fieldDefinition
+   *   The field definition.
+   * @param \Drupal\Core\Entity\EntityInterface|null $entity
+   *   (optional) The entity to which the file is to be uploaded, if it exists.
+   *   If the entity does not exist and it is not given, create access to the
+   *   file will be checked.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The file upload access result.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   */
+  public function checkFileUploadAccess(FieldDefinitionInterface $fieldDefinition, EntityInterface $entity = NULL): AccessResultInterface {
+    assert(is_null($entity) || $fieldDefinition->getTargetEntityTypeId() === $entity->getEntityTypeId() && $fieldDefinition->getTargetBundle() === $entity->bundle());
+    $entity_access_control_handler = $this->entityTypeManager->getAccessControlHandler($fieldDefinition->getTargetEntityTypeId());
+    $bundle = $this->entityTypeManager->getDefinition($fieldDefinition->getTargetEntityTypeId())
+      ->hasKey('bundle') ? $fieldDefinition->getTargetBundle() : NULL;
+    $entity_access_result = $entity
+      ? $entity_access_control_handler->access($entity, 'update', $this->currentUser, TRUE)
+      : $entity_access_control_handler->createAccess($bundle, $this->currentUser, [], TRUE);
+    $field_access_result = $entity_access_control_handler->fieldAccess('edit', $fieldDefinition, NULL, NULL, TRUE);
+    return $entity_access_result->andIf($field_access_result);
+  }
+
+}
diff --git a/core/modules/file/src/Upload/FileFieldUploadHandler.php b/core/modules/file/src/Upload/FileFieldUploadHandler.php
new file mode 100644
index 0000000000..cced6f2c83
--- /dev/null
+++ b/core/modules/file/src/Upload/FileFieldUploadHandler.php
@@ -0,0 +1,164 @@
+<?php
+
+namespace Drupal\file\Upload;
+
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\File\Exception\DirectoryNotReadyException;
+use Drupal\Core\File\FileSystemInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
+use Drupal\Core\Utility\Token;
+use Drupal\Component\Render\PlainTextOutput;
+use Drupal\Component\Utility\Bytes;
+use Drupal\Component\Utility\Environment;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\file\Plugin\Field\FieldType\FileFieldItemList;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\Mime\MimeTypeGuesserInterface;
+use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
+
+/**
+ * Provides an upload handler for file fields.
+ */
+class FileFieldUploadHandler extends FileUploadHandler {
+
+  /**
+   * The file upload handler.
+   *
+   * @var \Drupal\file\Upload\FileUploadHandler
+   */
+  protected $fileUploadHandler;
+
+  /**
+   * The token replacement instance.
+   *
+   * @var \Drupal\Core\Utility\Token
+   */
+  protected $token;
+
+  /**
+   * Constructs a FileUploadHandler object.
+   *
+   * @param \Drupal\Core\File\FileSystemInterface $fileSystem
+   *   The file system service.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
+   *   The entity type manager.
+   * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $streamWrapperManager
+   *   The stream wrapper manager.
+   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
+   *   The event dispatcher.
+   * @param \Symfony\Component\Mime\MimeTypeGuesserInterface $mimeTypeGuesser
+   *   The MIME type guesser.
+   * @param \Drupal\Core\Session\AccountInterface $currentUser
+   *   The current user.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
+   *   The request stack.
+   * @param \Drupal\Core\Utility\Token $token
+   *   The token service.
+   */
+  public function __construct(FileSystemInterface $fileSystem, EntityTypeManagerInterface $entityTypeManager, StreamWrapperManagerInterface $streamWrapperManager, EventDispatcherInterface $eventDispatcher, MimeTypeGuesserInterface $mimeTypeGuesser, AccountInterface $currentUser, RequestStack $requestStack, Token $token) {
+    parent::__construct($fileSystem, $entityTypeManager, $streamWrapperManager, $eventDispatcher, $mimeTypeGuesser, $currentUser, $requestStack);
+    $this->token = $token;
+  }
+
+  /**
+   * Handles file field uploads.
+   *
+   * @param \Drupal\file\Upload\UploadedFileInterface $uploadedFile
+   *   The uploaded file.
+   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+   *   The field definition.
+   *
+   * @return \Drupal\file\Upload\FileUploadResult
+   *   The created file entity.
+   *
+   * @throws \Symfony\Component\HttpFoundation\File\Exception\FileException
+   *   Thrown when a file upload error occurred.
+   * @throws \Drupal\Core\File\Exception\FileWriteException
+   *   Thrown when there is an error moving the file.
+   * @throws \Drupal\Core\File\Exception\FileException
+   *   Thrown when a file system error occurs.
+   * @throws \Drupal\file\Upload\FileValidationException
+   *   Thrown when file validation fails.
+   */
+  public function handleFileUploadForField(UploadedFileInterface $uploadedFile, FieldDefinitionInterface $field_definition): FileUploadResult {
+    assert(is_a($field_definition->getClass(), FileFieldItemList::class, TRUE));
+
+    $settings = $field_definition->getSettings();
+
+    $validators = $this->getUploadValidators($field_definition);
+    $destination = $this->getUploadLocation($settings);
+
+    // Check the destination file path is writable.
+    if (!$this->fileSystem->prepareDirectory($destination, FileSystemInterface::CREATE_DIRECTORY)) {
+      throw new DirectoryNotReadyException('Destination file path %s is not writable');
+    }
+
+    return $this->handleFileUpload($uploadedFile, $validators, $destination);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function moveUploadedFile($uploadedFile, $uri) {
+    return $this->fileSystem->move($uploadedFile->getPathname(), $uri);
+  }
+
+  /**
+   * Determines the URI for a file field.
+   *
+   * @param array $settings
+   *   The array of field settings.
+   *
+   * @return string
+   *   An un-sanitized file directory URI with tokens replaced. The result of
+   *   the token replacement is then converted to plain text and returned.
+   */
+  protected function getUploadLocation(array $settings): string {
+    $destination = trim($settings['file_directory'], '/');
+
+    // Replace tokens. As the tokens might contain HTML we convert it to plain
+    // text.
+    $destination = PlainTextOutput::renderFromHtml($this->token->replace($destination, [], [], new BubbleableMetadata()));
+    return $settings['uri_scheme'] . '://' . $destination;
+  }
+
+  /**
+   * Retrieves the upload validators for a field definition.
+   *
+   * This is copied from \Drupal\file\Plugin\Field\FieldType\FileItem as there
+   * is no entity instance available here that a FileItem would exist for.
+   *
+   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+   *   The field definition for which to get validators.
+   *
+   * @return array
+   *   An array suitable for passing to file_save_upload() or the file field
+   *   element's '#upload_validators' property.
+   */
+  protected function getUploadValidators(FieldDefinitionInterface $field_definition): array {
+    $validators = [
+      // Add in our check of the file name length.
+      'file_validate_name_length' => [],
+    ];
+    $settings = $field_definition->getSettings();
+
+    // Cap the upload size according to the PHP limit.
+    $max_filesize = Bytes::toNumber(Environment::getUploadMaxSize());
+    if (!empty($settings['max_filesize'])) {
+      $max_filesize = min($max_filesize, Bytes::toNumber($settings['max_filesize']));
+    }
+
+    // There is always a file size limit due to the PHP server limit.
+    $validators['file_validate_size'] = [$max_filesize];
+
+    // Add the extension check if necessary.
+    if (!empty($settings['file_extensions'])) {
+      $validators['file_validate_extensions'] = [$settings['file_extensions']];
+    }
+
+    return $validators;
+  }
+
+}
diff --git a/core/modules/file/src/Upload/FileUploadHandler.php b/core/modules/file/src/Upload/FileUploadHandler.php
index bcfc983002..0b0e7041b6 100644
--- a/core/modules/file/src/Upload/FileUploadHandler.php
+++ b/core/modules/file/src/Upload/FileUploadHandler.php
@@ -21,7 +21,6 @@
 use Symfony\Component\HttpFoundation\File\Exception\NoFileException;
 use Symfony\Component\HttpFoundation\File\Exception\NoTmpDirFileException;
 use Symfony\Component\HttpFoundation\File\Exception\PartialFileException;
-use Symfony\Component\HttpFoundation\File\UploadedFile;
 use Symfony\Component\HttpFoundation\RequestStack;
 use Symfony\Component\Mime\MimeTypeGuesserInterface;
 
@@ -115,7 +114,7 @@ public function __construct(FileSystemInterface $fileSystem, EntityTypeManagerIn
   /**
    * Creates a file from an upload.
    *
-   * @param \Symfony\Component\HttpFoundation\File\UploadedFile $uploadedFile
+   * @param \Drupal\file\Upload\UploadedFileInterface $uploadedFile
    *   The uploaded file object.
    * @param array $validators
    *   The validators to run against the uploaded file.
@@ -131,16 +130,9 @@ public function __construct(FileSystemInterface $fileSystem, EntityTypeManagerIn
    * @return \Drupal\file\Upload\FileUploadResult
    *   The created file entity.
    *
-   * @throws \Symfony\Component\HttpFoundation\File\Exception\FileException
-   *   Thrown when a file upload error occurred.
-   * @throws \Drupal\Core\File\Exception\FileWriteException
-   *   Thrown when there is an error moving the file.
-   * @throws \Drupal\Core\File\Exception\FileException
-   *   Thrown when a file system error occurs.
-   * @throws \Drupal\file\Upload\FileValidationException
-   *   Thrown when file validation fails.
+   * @throws \Drupal\Core\Entity\EntityStorageException
    */
-  public function handleFileUpload(UploadedFile $uploadedFile, array $validators = [], string $destination = 'temporary://', int $replace = FileSystemInterface::EXISTS_REPLACE): FileUploadResult {
+  public function handleFileUpload(UploadedFileInterface $uploadedFile, array $validators = [], string $destination = 'temporary://', int $replace = FileSystemInterface::EXISTS_REPLACE): FileUploadResult {
     $originalName = $uploadedFile->getClientOriginalName();
 
     if (!$uploadedFile->isValid()) {
@@ -217,7 +209,8 @@ public function handleFileUpload(UploadedFile $uploadedFile, array $validators =
     }
 
     $file->setFileUri($destinationFilename);
-    if (!$this->fileSystem->moveUploadedFile($uploadedFile->getRealPath(), $file->getFileUri())) {
+
+    if (!$this->moveUploadedFile($uploadedFile, $file->getFileUri())) {
       throw new FileWriteException('File upload error. Could not move uploaded file.');
     }
 
@@ -270,6 +263,21 @@ public function handleFileUpload(UploadedFile $uploadedFile, array $validators =
     return $result;
   }
 
+  /**
+   * Move the uploaded file from the temporary path to the destination.
+   *
+   * @param \Drupal\file\Upload\UploadedFileInterface $uploadedFile
+   *   The uploaded file.
+   * @param string $uri
+   *   The destination URI.
+   *
+   * @return bool
+   *   Returns FALSE if moving failed.
+   */
+  protected function moveUploadedFile(UploadedFileInterface $uploadedFile, string $uri) {
+    return $this->fileSystem->moveUploadedFile($uploadedFile->getRealPath(), $uri);
+  }
+
   /**
    * Gets the list of allowed extensions and updates the validators.
    *
diff --git a/core/modules/file/src/Upload/FilenameExtractor.php b/core/modules/file/src/Upload/FilenameExtractor.php
new file mode 100644
index 0000000000..110c166393
--- /dev/null
+++ b/core/modules/file/src/Upload/FilenameExtractor.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace Drupal\file\Upload;
+
+use Drupal\Core\File\FileSystemInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
+
+/**
+ * Extracts the filename from the request headers.
+ */
+class FilenameExtractor {
+
+  /**
+   * The file system.
+   *
+   * @var \Drupal\Core\File\FileSystemInterface
+   */
+  protected $fileSystem;
+
+  /**
+   * The regex used to extract the filename from the content disposition header.
+   *
+   * @var string
+   */
+  const REQUEST_HEADER_FILENAME_REGEX = '@\bfilename(?<star>\*?)=\"(?<filename>.+)\"@';
+
+  /**
+   * @param \Drupal\Core\File\FileSystemInterface $fileSystem
+   */
+  public function __construct(FileSystemInterface $fileSystem) {
+    $this->fileSystem = $fileSystem;
+  }
+
+  /**
+   * Validate and extract the filename from the request headers.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request.
+   *
+   * @return string
+   *   The filename.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
+   *   Thrown when there is an error extracting the filename.
+   */
+  public function extractFilename(Request $request): string {
+    // First, check the header exists.
+    if (!$request->headers->has('content-disposition')) {
+      throw new BadRequestHttpException('"Content-Disposition" header is required. A file name in the format "filename=FILENAME" must be provided.');
+    }
+
+    $content_disposition = $request->headers->get('content-disposition');
+
+    // Parse the header value. This regex does not allow an empty filename.
+    // i.e. 'filename=""'. This also matches on a word boundary so other keys
+    // like 'not_a_filename' don't work.
+    if (!preg_match(static::REQUEST_HEADER_FILENAME_REGEX, $content_disposition, $matches)) {
+      throw new BadRequestHttpException('No filename found in "Content-Disposition" header. A file name in the format "filename=FILENAME" must be provided.');
+    }
+
+    // Check for the "filename*" format. This is currently unsupported.
+    if (!empty($matches['star'])) {
+      throw new BadRequestHttpException('The extended "filename*" format is currently not supported in the "Content-Disposition" header.');
+    }
+
+    // Don't validate the actual filename here, that will be done by the upload
+    // validators in validate().
+    // @see \Drupal\file\Plugin\rest\resource\FileUploadResource::validate()
+    $filename = $matches['filename'];
+
+    // Make sure only the filename component is returned. Path information is
+    // stripped as per https://tools.ietf.org/html/rfc6266#section-4.3.
+    return $this->fileSystem->basename($filename);
+  }
+
+}
diff --git a/core/modules/file/src/Upload/FormUploadedFile.php b/core/modules/file/src/Upload/FormUploadedFile.php
new file mode 100644
index 0000000000..24eb8a3f3e
--- /dev/null
+++ b/core/modules/file/src/Upload/FormUploadedFile.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Drupal\file\Upload;
+
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+
+/**
+ * Provides a bridge to Symfony UploadedFile.
+ */
+class FormUploadedFile implements UploadedFileInterface {
+
+  /**
+   * The wrapped uploaded file.
+   *
+   * @var \Symfony\Component\HttpFoundation\File\UploadedFile
+   */
+  protected $uploadedFile;
+
+  /**
+   * Creates a new FormUploadedFile.
+   *
+   * @param \Symfony\Component\HttpFoundation\File\UploadedFile $uploadedFile
+   *   The wrapped Symfony uploaded file.
+   */
+  public function __construct(UploadedFile $uploadedFile) {
+    $this->uploadedFile = $uploadedFile;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getClientOriginalName(): string {
+    return $this->uploadedFile->getClientOriginalName();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isValid(): bool {
+    return $this->uploadedFile->isValid();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getErrorMessage(): string {
+    return $this->uploadedFile->getErrorMessage();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getError(): int {
+    return $this->uploadedFile->getError();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSize(): int {
+    return $this->uploadedFile->getSize();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRealPath() {
+    return $this->uploadedFile->getRealPath();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPathname() {
+    return $this->uploadedFile->getPathname();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFilename() {
+    return $this->uploadedFile->getFilename();
+  }
+
+}
diff --git a/core/modules/file/src/Upload/RawFileUploader.php b/core/modules/file/src/Upload/RawFileUploader.php
new file mode 100644
index 0000000000..cc6bfb15c7
--- /dev/null
+++ b/core/modules/file/src/Upload/RawFileUploader.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace Drupal\file\Upload;
+
+use Drupal\Core\File\FileSystemInterface;
+
+/**
+ * Uploads raw file data.
+ */
+class RawFileUploader {
+
+  /**
+   * The file system.
+   *
+   * @var \Drupal\Core\File\FileSystemInterface
+   */
+  protected $fileSystem;
+
+  /**
+   * The amount of bytes to read in each iteration when streaming file data.
+   *
+   * @var int
+   */
+  const BYTES_TO_READ = 8192;
+
+  /**
+   * @param \Drupal\Core\File\FileSystemInterface $fileSystem
+   */
+  public function __construct(FileSystemInterface $fileSystem) {
+    $this->fileSystem = $fileSystem;
+  }
+
+  /**
+   * Upload file data from the input stream.
+   *
+   * @param string $filename
+   *   The original file name of the uploaded file
+   * @param string $source
+   *   (Optional) The source of the upload. Defaults to 'php://input'.
+   * @param string|null $mimeType
+   *   (Optional) The type of the file as provided by PHP; null defaults to
+   *   application/octet-stream
+   *
+   * @return \Drupal\file\Upload\RawUploadedFile
+   *   The raw uploaded file.
+   */
+  public function uploadRawFile(string $filename, string $source = 'php://input', string $mimeType = 'application/octet-stream'): RawUploadedFile {
+    // 'rb' is needed so reading works correctly on Windows environments too.
+    $file_data = fopen($source, 'rb');
+
+    $temp_file_path = $this->fileSystem->tempnam('temporary://', 'file');
+    if ($temp_file_path === FALSE) {
+      return new RawUploadedFile('temporary://', $filename, $mimeType, \UPLOAD_ERR_NO_TMP_DIR);
+    }
+
+    $temp_file = fopen($temp_file_path, 'wb');
+
+    if ($temp_file === FALSE) {
+      // Close the input file stream since we can't proceed with the upload.
+      // Don't try to close $temp_file since it's FALSE at this point.
+      fclose($file_data);
+      return new RawUploadedFile($temp_file_path, $filename, $mimeType, \UPLOAD_ERR_NO_FILE);
+    }
+
+    while (!feof($file_data)) {
+      $read = fread($file_data, static::BYTES_TO_READ);
+
+      if ($read === FALSE) {
+        // Close the file streams.
+        fclose($temp_file);
+        fclose($file_data);
+        return new RawUploadedFile($temp_file, $filename, $mimeType, \UPLOAD_ERR_NO_FILE);
+      }
+
+      if (fwrite($temp_file, $read) === FALSE) {
+        // Close the file streams.
+        fclose($temp_file);
+        fclose($file_data);
+        return new RawUploadedFile($temp_file_path, $filename, $mimeType, \UPLOAD_ERR_CANT_WRITE);
+      }
+    }
+
+    // Close the temp file stream.
+    fclose($temp_file);
+
+    // Close the input stream.
+    fclose($file_data);
+
+    return new RawUploadedFile($temp_file_path, $filename);
+  }
+
+}
diff --git a/core/modules/file/src/Upload/RawUploadedFile.php b/core/modules/file/src/Upload/RawUploadedFile.php
new file mode 100644
index 0000000000..ac992c6544
--- /dev/null
+++ b/core/modules/file/src/Upload/RawUploadedFile.php
@@ -0,0 +1,121 @@
+<?php
+
+namespace Drupal\file\Upload;
+
+use Symfony\Component\HttpFoundation\File\File;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
+
+/**
+ * Implements a raw uploaded file that was not uploaded via a form.
+ *
+ * @see https://www.php.net/manual/en/wrappers.php.php#wrappers.php.input
+ */
+class RawUploadedFile extends File implements UploadedFileInterface {
+
+  /**
+   * The original name provided by the client.
+   *
+   * @var string
+   */
+  protected $originalName;
+
+  /**
+   * The untrusted mimetype provided by the client.
+   *
+   * @var string
+   */
+  protected $mimeType;
+
+  /**
+   * The error code.
+   *
+   * @var int
+   */
+  protected $error;
+
+  /**
+   * @param string $path
+   * @param string $originalName
+   * @param string $mimeType
+   * @param int $error
+   */
+  public function __construct(string $path, string $originalName, string $mimeType = 'application/octet-stream', int $error = \UPLOAD_ERR_OK) {
+    parent::__construct($path, \UPLOAD_ERR_OK === $this->error);
+    $this->originalName = $originalName;
+    $this->mimeType = $mimeType;
+    $this->error = $error;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getClientOriginalName(): string {
+    return $this->originalName;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isValid(): bool {
+    return \UPLOAD_ERR_OK === $this->error;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @see \Symfony\Component\HttpFoundation\File\UploadedFile::getErrorMessage()
+   */
+  public function getErrorMessage(): string {
+    static $errors = [
+      \UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).',
+      \UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.',
+      \UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.',
+      \UPLOAD_ERR_NO_FILE => 'No file was uploaded.',
+      \UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.',
+      \UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.',
+      \UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension.',
+    ];
+
+    $errorCode = $this->error;
+    $maxFilesize = \UPLOAD_ERR_INI_SIZE === $errorCode ? UploadedFile::getMaxFilesize() / 1024 : 0;
+    $message = $errors[$errorCode] ?? 'The file "%s" was not uploaded due to an unknown error.';
+
+    return sprintf($message, $this->getClientOriginalName(), $maxFilesize);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getError(): int {
+    return $this->error;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSize(): int {
+    return parent::getSize();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRealPath() {
+    return parent::getRealPath();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPathname() {
+    return parent::getPathname();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFilename() {
+    return parent::getFilename();
+  }
+
+}
diff --git a/core/modules/file/src/Upload/UploadedFileInterface.php b/core/modules/file/src/Upload/UploadedFileInterface.php
new file mode 100644
index 0000000000..b544a8a8ec
--- /dev/null
+++ b/core/modules/file/src/Upload/UploadedFileInterface.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Drupal\file\Upload;
+
+/**
+ * Provides an interface for uploaded files.
+ */
+interface UploadedFileInterface {
+
+  /**
+   * Returns the original file name.
+   *
+   * It is extracted from the request from which the file has been uploaded.
+   * Then it should not be considered as a safe value.
+   *
+   * @return string The original name
+   */
+  public function getClientOriginalName(): string;
+
+  /**
+   * Returns whether the file was uploaded successfully.
+   *
+   * @return bool True if the file has been uploaded with HTTP and no error occurred
+   */
+  public function isValid(): bool;
+
+  /**
+   * Returns an informative upload error message.
+   *
+   * @return string The error message regarding the specified error code
+   */
+  public function getErrorMessage(): string;
+
+  /**
+   * Returns the upload error.
+   *
+   * If the upload was successful, the constant UPLOAD_ERR_OK is returned.
+   * Otherwise one of the other UPLOAD_ERR_XXX constants is returned.
+   *
+   * @return int
+   *   The upload error
+   */
+  public function getError(): int;
+
+  /**
+   * Gets file size.
+   *
+   * @return int
+   *   The filesize in bytes.
+   *
+   * @see https://www.php.net/manual/en/splfileinfo.getsize.php
+   */
+  public function getSize(): int;
+
+  /**
+   * Gets absolute path to file.
+   *
+   * @return string|false
+   *   The path to the file, or <b>FALSE</b> if the file does not exist.
+   *
+   * @see https://php.net/manual/en/splfileinfo.getrealpath.php
+   */
+  public function getRealPath();
+
+  /**
+   * Gets the path to the file.
+   *
+   * @return string
+   *   The path to the file.
+   *
+   * @see https://php.net/manual/en/splfileinfo.getpathname.php
+   */
+  public function getPathname();
+
+  /**
+   * Gets the filename.
+   *
+   * @return string
+   *   The filename.
+   *
+   * @see https://php.net/manual/en/splfileinfo.getfilename.php
+   */
+  public function getFilename();
+
+}
diff --git a/core/modules/file/tests/src/Kernel/FileFieldUploadHandlerTest.php b/core/modules/file/tests/src/Kernel/FileFieldUploadHandlerTest.php
new file mode 100644
index 0000000000..16aaa055cf
--- /dev/null
+++ b/core/modules/file/tests/src/Kernel/FileFieldUploadHandlerTest.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace Drupal\Tests\file\Kernel;
+
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\file\Plugin\Field\FieldType\FileFieldItemList;
+use Drupal\file\Upload\RawUploadedFile;
+use org\bovigo\vfs\vfsStream;
+
+/**
+ * Tests the file field upload handler.
+ *
+ * @coversDefaultClass \Drupal\file\Upload\FileFieldUploadHandler
+ * @group file
+ */
+class FileFieldUploadHandlerTest extends FileManagedUnitTestBase {
+
+  /**
+   * The file field upload handler under test.
+   *
+   * @var \Drupal\file\Upload\FileFieldUploadHandler
+   */
+  protected $fileFieldUploadHandler;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $this->fileFieldUploadHandler = $this->container->get('file.field_upload_handler');
+  }
+
+  /**
+   * @covers ::handleFileUploadForField
+   */
+  public function testHandleFileUploadForField() {
+
+    $vfsRoot = vfsStream::setup();
+    $tempFile = vfsStream::newFile("temp1234")->at($vfsRoot);
+
+    $filename = $this->randomMachineName() . ".txt";
+    $uploadedFile = new RawUploadedFile($tempFile->url(), $filename);
+
+    $fileDir = $this->randomMachineName();
+
+    $fieldDefinition = $this->prophesize(FieldDefinitionInterface::class);
+    $fieldDefinition->getClass()
+      ->willReturn(FileFieldItemList::class);
+    $fieldDefinition->getSettings()
+      ->willReturn([
+        'file_directory' => $fileDir,
+        'uri_scheme' => "public",
+        'max_filesize' => "1024",
+      ]);
+
+    $result = $this->fileFieldUploadHandler->handleFileUploadForField($uploadedFile, $fieldDefinition->reveal());
+
+    $this->assertNotNull($result);
+
+    $file = $result->getFile();
+    $this->assertNotNull($file);
+
+    $this->assertEquals($filename, $file->getFilename());
+    $this->assertEquals("public://$fileDir/$filename", $file->getFileUri());
+  }
+
+}
diff --git a/core/modules/file/tests/src/Kernel/FileUploadHandlerTest.php b/core/modules/file/tests/src/Kernel/FileUploadHandlerTest.php
index 5c8fb7fb0b..c5f3362c8e 100644
--- a/core/modules/file/tests/src/Kernel/FileUploadHandlerTest.php
+++ b/core/modules/file/tests/src/Kernel/FileUploadHandlerTest.php
@@ -3,9 +3,9 @@
 namespace Drupal\Tests\file\Kernel;
 
 use Drupal\Component\Utility\Environment;
+use Drupal\file\Upload\UploadedFileInterface;
 use Drupal\KernelTests\KernelTestBase;
 use Symfony\Component\HttpFoundation\File\Exception\FormSizeFileException;
-use Symfony\Component\HttpFoundation\File\UploadedFile;
 
 /**
  * Tests the file upload handler.
@@ -39,7 +39,7 @@ protected function setUp(): void {
    */
   public function testFileSaveUploadSingleErrorFormSize() {
     $file_name = $this->randomMachineName();
-    $file_info = $this->createMock(UploadedFile::class);
+    $file_info = $this->createMock(UploadedFileInterface::class);
     $file_info->expects($this->once())->method('getError')->willReturn(UPLOAD_ERR_FORM_SIZE);
     $file_info->expects($this->once())->method('getClientOriginalName')->willReturn($file_name);
     $file_info->expects($this->once())->method('getErrorMessage')->willReturn(sprintf('The file "%s" could not be saved because it exceeds %s, the maximum allowed size for uploads.', $file_name, format_size(Environment::getUploadMaxSize())));
diff --git a/core/modules/file/tests/src/Unit/FileFieldDefinitionResolverTest.php b/core/modules/file/tests/src/Unit/FileFieldDefinitionResolverTest.php
new file mode 100644
index 0000000000..884dbd81a0
--- /dev/null
+++ b/core/modules/file/tests/src/Unit/FileFieldDefinitionResolverTest.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Drupal\Tests\file\Unit;
+
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\Exception\UnknownFieldException;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\file\FileFieldDefinitionResolver;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests for file field definition resolver.
+ *
+ * @coversDefaultClass \Drupal\file\FileFieldDefinitionResolver
+ * @group file
+ */
+class FileFieldDefinitionResolverTest extends UnitTestCase {
+
+  /**
+   * @covers ::resolveFieldDefinition
+   */
+  public function testResolveFieldDefinitionSuccess() {
+
+    $field_definition = $this->prophesize(FieldDefinitionInterface::class);
+    $field_definition->getSetting('target_type')
+      ->willReturn("file");
+
+    $definitions = [
+      "baz" => $field_definition->reveal(),
+    ];
+
+    $entityFieldManager = $this->prophesize(EntityFieldManagerInterface::class);
+    $entityFieldManager->getFieldDefinitions("foo", "bar")
+      ->willReturn($definitions);
+    $resolver = new FileFieldDefinitionResolver($entityFieldManager->reveal());
+
+    $definition = $resolver->resolveFieldDefinition("foo", "bar", "baz");
+
+    $this->assertNotNull($definition);
+  }
+
+  /**
+   * @covers ::resolveFieldDefinition
+   */
+  public function testResolveFieldDefinitionUnknownField() {
+
+    $entityFieldManager = $this->prophesize(EntityFieldManagerInterface::class);
+    $entityFieldManager->getFieldDefinitions("foo", "bar")
+      ->willReturn([]);
+
+    $resolver = new FileFieldDefinitionResolver($entityFieldManager->reveal());
+
+    $field_name = "baz";
+
+    $this->expectException(UnknownFieldException::class);
+    $this->expectExceptionMessage(sprintf('Field "%s" does not exist.', $field_name));
+
+    $resolver->resolveFieldDefinition("foo", "bar", $field_name);
+  }
+
+  /**
+   * @covers ::resolveFieldDefinition
+   */
+  public function testResolveFieldDefinitionInvalidTargetType() {
+
+    $field_definition = $this->prophesize(FieldDefinitionInterface::class);
+    $field_definition->getSetting('target_type')
+      ->willReturn("whiz");
+
+    $field_name = "baz";
+
+    $definitions = [
+      $field_name => $field_definition->reveal(),
+    ];
+
+    $entityFieldManager = $this->prophesize(EntityFieldManagerInterface::class);
+    $entityFieldManager->getFieldDefinitions("foo", "bar")
+      ->willReturn($definitions);
+    $resolver = new FileFieldDefinitionResolver($entityFieldManager->reveal());
+
+    $this->expectException(\InvalidArgumentException::class);
+    $this->expectExceptionMessage(sprintf('"%s" is not a file field', $field_name));
+    $resolver->resolveFieldDefinition("foo", "bar", $field_name);
+
+  }
+
+}
diff --git a/core/modules/file/tests/src/Unit/Upload/FileFieldUploadAccessCheckerTest.php b/core/modules/file/tests/src/Unit/Upload/FileFieldUploadAccessCheckerTest.php
new file mode 100644
index 0000000000..4579796fb4
--- /dev/null
+++ b/core/modules/file/tests/src/Unit/Upload/FileFieldUploadAccessCheckerTest.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace Drupal\Tests\file\Unit\Upload;
+
+use Drupal\Core\Access\AccessResultAllowed;
+use Drupal\Core\Entity\EntityAccessControlHandlerInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\file\Upload\FileFieldUploadAccessChecker;
+use Drupal\Tests\UnitTestCase;
+use Prophecy\Argument;
+
+/**
+ * Tests the file field upload access checker.
+ *
+ * @coversDefaultClass \Drupal\file\Upload\FileFieldUploadAccessChecker
+ * @group file
+ */
+class FileFieldUploadAccessCheckerTest extends UnitTestCase {
+
+  /**
+   * @covers ::checkFileUploadAccess
+   */
+  public function testCheckAccessSuccess() {
+
+    $entityAccess = new AccessResultAllowed();
+    $fieldAccess = new AccessResultAllowed();
+
+    $entityTypeId = "foo";
+    $bundle = "bar";
+    $currentUser = $this->prophesize(AccountInterface::class);
+
+    $fieldDefinition = $this->prophesize(FieldDefinitionInterface::class);
+    $fieldDefinition->getTargetEntityTypeId()
+      ->willReturn($entityTypeId);
+    $fieldDefinition->getTargetBundle()
+      ->willReturn($bundle);
+
+    $entityAccessControlHandler = $this->prophesize(EntityAccessControlHandlerInterface::class);
+    $entityAccessControlHandler->createAccess($bundle, $currentUser->reveal(), [], TRUE)
+      ->willReturn($entityAccess);
+    $entityAccessControlHandler->fieldAccess("edit", $fieldDefinition->reveal(), NULL, NULL, TRUE)
+      ->willReturn($fieldAccess);
+
+    $entityType = $this->prophesize(EntityTypeInterface::class);
+    $entityType->hasKey(Argument::any())
+      ->willReturn(TRUE);
+
+    $entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
+    $entityTypeManager->getAccessControlHandler($entityTypeId)
+      ->willReturn($entityAccessControlHandler->reveal());
+    $entityTypeManager->getDefinition($entityTypeId)
+      ->willReturn($entityType->reveal());
+
+    $checker = new FileFieldUploadAccessChecker($entityTypeManager->reveal(), $currentUser->reveal());
+
+    $accessResult = $checker->checkFileUploadAccess($fieldDefinition->reveal());
+
+    $this->assertTrue($accessResult->isAllowed());
+  }
+
+  /**
+   * @covers ::checkFileUploadAccess
+   */
+  public function testCheckAccessWithEntitySuccess() {
+
+    $entityAccess = new AccessResultAllowed();
+    $fieldAccess = new AccessResultAllowed();
+
+    $bundle = "bar";
+    $entityTypeId = "foo";
+    $entity = $this->prophesize(EntityInterface::class);
+    $entity->bundle()
+      ->willReturn($bundle);
+    $entity->getEntityTypeId()
+      ->willReturn($entityTypeId);
+
+    $currentUser = $this->prophesize(AccountInterface::class);
+
+    $fieldDefinition = $this->prophesize(FieldDefinitionInterface::class);
+    $fieldDefinition->getTargetEntityTypeId()
+      ->willReturn($entityTypeId);
+    $fieldDefinition->getTargetBundle()
+      ->willReturn($bundle);
+
+    $entityAccessControlHandler = $this->prophesize(EntityAccessControlHandlerInterface::class);
+    $entityAccessControlHandler->access($entity->reveal(), "update", $currentUser->reveal(), TRUE)
+      ->willReturn($entityAccess);
+    $entityAccessControlHandler->fieldAccess("edit", $fieldDefinition->reveal(), NULL, NULL, TRUE)
+      ->willReturn($fieldAccess);
+
+    $entityType = $this->prophesize(EntityTypeInterface::class);
+    $entityType->hasKey(Argument::any())
+      ->willReturn(TRUE);
+
+    $entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
+    $entityTypeManager->getAccessControlHandler($entityTypeId)
+      ->willReturn($entityAccessControlHandler->reveal());
+    $entityTypeManager->getDefinition($entityTypeId)
+      ->willReturn($entityType->reveal());
+
+    $checker = new FileFieldUploadAccessChecker($entityTypeManager->reveal(), $currentUser->reveal());
+
+    $accessResult = $checker->checkFileUploadAccess($fieldDefinition->reveal(), $entity->reveal());
+
+    $this->assertTrue($accessResult->isAllowed());
+  }
+
+}
diff --git a/core/modules/file/tests/src/Unit/Upload/FilenameExtractorTest.php b/core/modules/file/tests/src/Unit/Upload/FilenameExtractorTest.php
new file mode 100644
index 0000000000..924b36b8b4
--- /dev/null
+++ b/core/modules/file/tests/src/Unit/Upload/FilenameExtractorTest.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace Drupal\Tests\file\Unit\Upload;
+
+use Drupal\Core\File\FileSystemInterface;
+use Drupal\file\Upload\FilenameExtractor;
+use Drupal\Tests\UnitTestCase;
+use Prophecy\Argument;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
+
+/**
+ * Tests the filename extractor.
+ *
+ * @coversDefaultClass \Drupal\file\Upload\FilenameExtractor
+ * @group file
+ */
+class FilenameExtractorTest extends UnitTestCase {
+
+  /**
+   * @covers ::extractFilename
+   */
+  public function testExtractFilenameSuccess() {
+
+    $fileSystem = $this->prophesize(FileSystemInterface::class);
+    $fileSystem->basename(Argument::any())
+      ->willReturn("foo.txt");
+
+    $extractor = new FilenameExtractor($fileSystem->reveal());
+
+    $request = new Request();
+    $request->headers->add(['content-disposition' => 'filename="foo.txt"']);
+
+    $filename = $extractor->extractFilename($request);
+
+    $this->assertEquals("foo.txt", $filename);
+  }
+
+  /**
+   * @covers ::extractFilename
+   */
+  public function testExtractFilenameMissingHeader() {
+
+    $fileSystem = $this->prophesize(FileSystemInterface::class);
+
+    $extractor = new FilenameExtractor($fileSystem->reveal());
+
+    $request = new Request();
+
+    $this->expectException(BadRequestHttpException::class);
+    $this->expectExceptionMessage('"Content-Disposition" header is required. A file name in the format "filename=FILENAME" must be provided.');
+
+    $extractor->extractFilename($request);
+  }
+
+  /**
+   * @covers ::extractFilename
+   */
+  public function testExtractFilenameBadHeader() {
+
+    $fileSystem = $this->prophesize(FileSystemInterface::class);
+
+    $extractor = new FilenameExtractor($fileSystem->reveal());
+
+    $request = new Request();
+    $request->headers->add(['content-disposition' => 'foo']);
+
+    $this->expectException(BadRequestHttpException::class);
+    $this->expectExceptionMessage('No filename found in "Content-Disposition" header. A file name in the format "filename=FILENAME" must be provided.');
+
+    $extractor->extractFilename($request);
+  }
+
+  /**
+   * @covers ::extractFilename
+   */
+  public function testExtractFilenameBadHeaderWithStar() {
+
+    $fileSystem = $this->prophesize(FileSystemInterface::class);
+
+    $extractor = new FilenameExtractor($fileSystem->reveal());
+
+    $request = new Request();
+    $request->headers->add(['content-disposition' => 'filename*="foo.txt"']);
+
+    $this->expectException(BadRequestHttpException::class);
+    $this->expectExceptionMessage('The extended "filename*" format is currently not supported in the "Content-Disposition" header.');
+
+    $extractor->extractFilename($request);
+  }
+
+}
diff --git a/core/modules/file/tests/src/Unit/Upload/RawFileUploaderTest.php b/core/modules/file/tests/src/Unit/Upload/RawFileUploaderTest.php
new file mode 100644
index 0000000000..57d8f031e6
--- /dev/null
+++ b/core/modules/file/tests/src/Unit/Upload/RawFileUploaderTest.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\Tests\file\Unit\Upload;
+
+use Drupal\Core\File\FileSystemInterface;
+use Drupal\file\Upload\RawFileUploader;
+use Drupal\Tests\UnitTestCase;
+use org\bovigo\vfs\vfsStream;
+use Prophecy\Argument;
+
+/**
+ * Tests the file stream uploader.
+ *
+ * @coversDefaultClass \Drupal\file\Upload\RawFileUploader
+ * @group file
+ */
+class RawFileUploaderTest extends UnitTestCase {
+
+  public function testUpload() {
+
+    $vfsRoot = vfsStream::setup();
+    $tempFile = vfsStream::newFile("temp1234")->at($vfsRoot);
+
+    $fileSystem = $this->prophesize(FileSystemInterface::class);
+    $fileSystem->tempnam(Argument::any(), Argument::any())
+      ->willReturn($tempFile->url());
+
+    $uploader = new RawFileUploader($fileSystem->reveal());
+
+    $filename = "foo.txt";
+    $input = vfsStream::newFile($filename)->at($vfsRoot)->setContent("foo");
+
+    $uploadedFile = $uploader->uploadRawFile($filename, $input->url());
+
+    $this->assertNotNull($uploadedFile);
+    $this->assertEquals($filename, $uploadedFile->getClientOriginalName());
+    $this->assertEquals(\UPLOAD_ERR_OK, $uploadedFile->getError());
+  }
+
+}
diff --git a/core/modules/jsonapi/jsonapi.info.yml b/core/modules/jsonapi/jsonapi.info.yml
index 4d9257c4fc..5296de6b25 100644
--- a/core/modules/jsonapi/jsonapi.info.yml
+++ b/core/modules/jsonapi/jsonapi.info.yml
@@ -6,3 +6,4 @@ version: VERSION
 configure: jsonapi.settings
 dependencies:
   - drupal:serialization
+  - drupal:file
diff --git a/core/modules/jsonapi/jsonapi.services.yml b/core/modules/jsonapi/jsonapi.services.yml
index 8e5f4a8fd5..e08720c657 100644
--- a/core/modules/jsonapi/jsonapi.services.yml
+++ b/core/modules/jsonapi/jsonapi.services.yml
@@ -195,6 +195,12 @@ services:
       - '@entity_field.manager'
       - '@jsonapi.file.uploader.field'
       - '@http_kernel'
+      - '@file.field_upload_handler'
+      - '@file.field_definition_resolver'
+      - '@file.upload_access_checker'
+      - '@file.upload_filename_extractor'
+      - '@file.raw_file_uploader'
+      - '@logger.channel.file'
 
   # Event subscribers.
   jsonapi.custom_query_parameter_names_validator.subscriber:
diff --git a/core/modules/jsonapi/src/Controller/FileUpload.php b/core/modules/jsonapi/src/Controller/FileUpload.php
index c11244a799..2454a4daef 100644
--- a/core/modules/jsonapi/src/Controller/FileUpload.php
+++ b/core/modules/jsonapi/src/Controller/FileUpload.php
@@ -5,12 +5,20 @@
 use Drupal\Component\Render\PlainTextOutput;
 use Drupal\Core\Access\AccessResultReasonInterface;
 use Drupal\Core\Cache\CacheableMetadata;
-use Drupal\Core\Entity\EntityConstraintViolationListInterface;
 use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\Exception\UnknownFieldException;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\File\Exception\FileException;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Url;
+use Drupal\file\FileInterface;
+use Drupal\file\FileFieldDefinitionResolver;
+use Drupal\file\Upload\FileFieldUploadAccessChecker;
+use Drupal\file\Upload\FileFieldUploadHandler;
+use Drupal\file\Upload\FilenameExtractor;
+use Drupal\file\Upload\RawFileUploader;
+use Drupal\file\Upload\FileValidationException;
 use Drupal\jsonapi\Entity\EntityValidationTrait;
 use Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel;
 use Drupal\jsonapi\JsonApiResource\Link;
@@ -20,22 +28,23 @@
 use Drupal\jsonapi\JsonApiResource\ResourceObjectData;
 use Drupal\jsonapi\ResourceResponse;
 use Drupal\jsonapi\ResourceType\ResourceType;
-use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\HttpFoundation\File\Exception\FileException as SymfonyFileException;
 use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\HttpKernel\Exception\HttpException;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
 use Symfony\Component\HttpKernel\HttpKernelInterface;
-use Symfony\Component\Validator\ConstraintViolationInterface;
 
 /**
  * Handles file upload requests.
  *
- * @internal JSON:API maintains no PHP API. The API is the HTTP API. This class
- *   may change at any time and could break any dependencies on it.
- *
  * @see https://www.drupal.org/project/drupal/issues/3032787
  * @see jsonapi.api.php
+ * @internal JSON:API maintains no PHP API. The API is the HTTP API. This class
+ *   may change at any time and could break any dependencies on it.
  */
 class FileUpload {
 
@@ -69,6 +78,46 @@ class FileUpload {
    */
   protected $httpKernel;
 
+  /**
+   * The file field upload handler.
+   *
+   * @var \Drupal\file\Upload\FileFieldUploadHandler
+   */
+  protected $fileFieldUploadHandler;
+
+  /**
+   * @var \Drupal\file\FileFieldDefinitionResolver
+   */
+  protected $fieldResolver;
+
+  /**
+   * The file field upload access checker.
+   *
+   * @var \Drupal\file\Upload\FileFieldUploadAccessChecker
+   */
+  protected $uploadAccessChecker;
+
+  /**
+   * The filename extractor.
+   *
+   * @var \Drupal\file\Upload\FilenameExtractor
+   */
+  protected $filenameExtractor;
+
+  /**
+   * The file stream uploader.
+   *
+   * @var \Drupal\file\Upload\RawFileUploader
+   */
+  protected $fileStreamUploader;
+
+  /**
+   * The logger.
+   *
+   * @var \Psr\Log\LoggerInterface
+   */
+  protected $logger;
+
   /**
    * Creates a new FileUpload instance.
    *
@@ -80,12 +129,30 @@ class FileUpload {
    *   The file uploader.
    * @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
    *   An HTTP kernel for making subrequests.
+   * @param \Drupal\file\Upload\FileFieldUploadHandler $upload_handler
+   *   The file field upload handler.
+   * @param \Drupal\file\FileFieldDefinitionResolver $field_resolver
+   *   The file field definition resolver.
+   * @param \Drupal\file\Upload\FileFieldUploadAccessChecker $access_checker
+   *   The file upload access checker.
+   * @param \Drupal\file\Upload\FilenameExtractor $filename_extractor
+   *   The filename extractor.
+   * @param \Drupal\file\Upload\RawFileUploader $file_stream_uploader
+   *   The file stream uploader.
+   * @param \Psr\Log\LoggerInterface $logger
+   *   The logger.
    */
-  public function __construct(AccountInterface $current_user, EntityFieldManagerInterface $field_manager, TemporaryJsonapiFileFieldUploader $file_uploader, HttpKernelInterface $http_kernel) {
+  public function __construct(AccountInterface $current_user, EntityFieldManagerInterface $field_manager, TemporaryJsonapiFileFieldUploader $file_uploader, HttpKernelInterface $http_kernel, FileFieldUploadHandler $upload_handler, FileFieldDefinitionResolver $field_resolver, FileFieldUploadAccessChecker $access_checker, FilenameExtractor $filename_extractor, RawFileUploader $file_stream_uploader, LoggerInterface $logger) {
     $this->currentUser = $current_user;
     $this->fieldManager = $field_manager;
     $this->fileUploader = $file_uploader;
     $this->httpKernel = $http_kernel;
+    $this->fileFieldUploadHandler = $upload_handler;
+    $this->fieldResolver = $field_resolver;
+    $this->uploadAccessChecker = $access_checker;
+    $this->filenameExtractor = $filename_extractor;
+    $this->fileStreamUploader = $file_stream_uploader;
+    $this->logger = $logger;
   }
 
   /**
@@ -111,23 +178,8 @@ public function __construct(AccountInterface $current_user, EntityFieldManagerIn
    *   Thrown if an exception occurs during a subrequest to fetch the newly
    *   created file entity.
    */
-  public function handleFileUploadForExistingResource(Request $request, ResourceType $resource_type, $file_field_name, FieldableEntityInterface $entity) {
-    $file_field_name = $resource_type->getInternalName($file_field_name);
-    $field_definition = $this->validateAndLoadFieldDefinition($resource_type->getEntityTypeId(), $resource_type->getBundle(), $file_field_name);
-
-    static::ensureFileUploadAccess($this->currentUser, $field_definition, $entity);
-
-    $filename = $this->fileUploader->validateAndParseContentDispositionHeader($request);
-    $file = $this->fileUploader->handleFileUploadForField($field_definition, $filename, $this->currentUser);
-
-    if ($file instanceof EntityConstraintViolationListInterface) {
-      $violations = $file;
-      $message = "Unprocessable Entity: file validation failed.\n";
-      $message .= implode("\n", array_map(function (ConstraintViolationInterface $violation) {
-        return PlainTextOutput::renderFromHtml($violation->getMessage());
-      }, (array) $violations->getIterator()));
-      throw new UnprocessableEntityHttpException($message);
-    }
+  public function handleFileUploadForExistingResource(Request $request, ResourceType $resource_type, string $file_field_name, FieldableEntityInterface $entity): Response {
+    $file = $this->doHandleFileUpload($request, $resource_type, $file_field_name);
 
     if ($resource_type->getFieldByInternalName($file_field_name)->hasOne()) {
       $entity->{$file_field_name} = $file;
@@ -140,7 +192,8 @@ public function handleFileUploadForExistingResource(Request $request, ResourceTy
 
     $route_parameters = ['entity' => $entity->uuid()];
     $route_name = sprintf('jsonapi.%s.%s.related', $resource_type->getTypeName(), $resource_type->getPublicName($file_field_name));
-    $related_url = Url::fromRoute($route_name, $route_parameters)->toString(TRUE);
+    $related_url = Url::fromRoute($route_name, $route_parameters)
+      ->toString(TRUE);
     $request = Request::create($related_url->getGeneratedUrl(), 'GET', [], $request->cookies->all(), [], $request->server->all());
     return $this->httpKernel->handle($request, HttpKernelInterface::SUB_REQUEST);
   }
@@ -161,23 +214,8 @@ public function handleFileUploadForExistingResource(Request $request, ResourceTy
    * @throws \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException
    *   Thrown when there are validation errors.
    */
-  public function handleFileUploadForNewResource(Request $request, ResourceType $resource_type, $file_field_name) {
-    $file_field_name = $resource_type->getInternalName($file_field_name);
-    $field_definition = $this->validateAndLoadFieldDefinition($resource_type->getEntityTypeId(), $resource_type->getBundle(), $file_field_name);
-
-    static::ensureFileUploadAccess($this->currentUser, $field_definition);
-
-    $filename = $this->fileUploader->validateAndParseContentDispositionHeader($request);
-    $file = $this->fileUploader->handleFileUploadForField($field_definition, $filename, $this->currentUser);
-
-    if ($file instanceof EntityConstraintViolationListInterface) {
-      $violations = $file;
-      $message = "Unprocessable Entity: file validation failed.\n";
-      $message .= implode("\n", array_map(function (ConstraintViolationInterface $violation) {
-        return PlainTextOutput::renderFromHtml($violation->getMessage());
-      }, iterator_to_array($violations)));
-      throw new UnprocessableEntityHttpException($message);
-    }
+  public function handleFileUploadForNewResource(Request $request, ResourceType $resource_type, string $file_field_name): Response {
+    $file = $this->doHandleFileUpload($request, $resource_type, $file_field_name);
 
     // @todo Remove line below in favor of commented line in https://www.drupal.org/project/drupal/issues/2878463.
     $self_link = new Link(new CacheableMetadata(), Url::fromRoute('jsonapi.file--file.individual', ['entity' => $file->uuid()]), 'self');
@@ -190,20 +228,65 @@ public function handleFileUploadForNewResource(Request $request, ResourceType $r
     return new ResourceResponse(new JsonApiDocumentTopLevel(new ResourceObjectData([$resource_object], 1), new NullIncludedData(), $links), 201, []);
   }
 
+  /**
+   * Handles common logic of file uploads.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The HTTP request object.
+   * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type
+   *   The JSON:API resource type for the current request.
+   * @param string $file_field_name
+   *   The file field for which the file is to be uploaded.
+   *
+   * @return \Drupal\file\FileInterface
+   */
+  protected function doHandleFileUpload(Request $request, ResourceType $resource_type, string $file_field_name): FileInterface {
+    $file_field_name = $resource_type->getInternalName($file_field_name);
+
+    try {
+      $field_definition = $this->fieldResolver->resolveFieldDefinition($resource_type->getEntityTypeId(), $resource_type->getBundle(), $file_field_name);
+    }
+    catch (UnknownFieldException $e) {
+      throw new NotFoundHttpException($e->getMessage(), $e);
+    }
+
+    $this->ensureFileUploadAccess($field_definition);
+
+    $filename = $this->filenameExtractor->extractFilename($request);
+    $uploadedFile = $this->fileStreamUploader->uploadRawFile($filename);
+
+    try {
+      $result = $this->fileFieldUploadHandler->handleFileUploadForField($uploadedFile, $field_definition);
+    }
+    catch (SymfonyFileException | FileException $e) {
+      $this->logger->error('Temporary file could not be created for file upload.');
+      throw new HttpException(500, $e->getMessage(), $e);
+    }
+    catch (FileValidationException $e) {
+      $message = "Unprocessable Entity: file validation failed.\n";
+      $message .= implode("\n", array_map(function (string $error) {
+        return PlainTextOutput::renderFromHtml($error);
+      }, $e->getErrors()));
+
+      throw new UnprocessableEntityHttpException($message);
+    }
+
+    return $result->getFile();
+  }
+
   /**
    * Ensures that the given account is allowed to upload a file.
    *
-   * @param \Drupal\Core\Session\AccountInterface $account
-   *   The account for which access should be checked.
    * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
    *   The field for which the file is to be uploaded.
    * @param \Drupal\Core\Entity\FieldableEntityInterface|null $entity
    *   The entity, if one exists, for which the file is to be uploaded.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   *   Thrown if access is denied.
    */
-  protected static function ensureFileUploadAccess(AccountInterface $account, FieldDefinitionInterface $field_definition, FieldableEntityInterface $entity = NULL) {
-    $access_result = $entity
-      ? TemporaryJsonapiFileFieldUploader::checkFileUploadAccess($account, $field_definition, $entity)
-      : TemporaryJsonapiFileFieldUploader::checkFileUploadAccess($account, $field_definition);
+  protected function ensureFileUploadAccess(FieldDefinitionInterface $field_definition, FieldableEntityInterface $entity = NULL): void {
+    $access_result = $this->uploadAccessChecker->checkFileUploadAccess($field_definition, $entity);
     if (!$access_result->isAllowed()) {
       $reason = 'The current user is not permitted to upload a file for this field.';
       if ($access_result instanceof AccessResultReasonInterface) {
@@ -213,38 +296,4 @@ protected static function ensureFileUploadAccess(AccountInterface $account, Fiel
     }
   }
 
-  /**
-   * Validates and loads a field definition instance.
-   *
-   * @param string $entity_type_id
-   *   The entity type ID the field is attached to.
-   * @param string $bundle
-   *   The bundle the field is attached to.
-   * @param string $field_name
-   *   The field name.
-   *
-   * @return \Drupal\Core\Field\FieldDefinitionInterface
-   *   The field definition.
-   *
-   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
-   *   Thrown when the field does not exist.
-   * @throws \Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException
-   *   Thrown when the target type of the field is not a file, or the current
-   *   user does not have 'edit' access for the field.
-   */
-  protected function validateAndLoadFieldDefinition($entity_type_id, $bundle, $field_name) {
-    $field_definitions = $this->fieldManager->getFieldDefinitions($entity_type_id, $bundle);
-    if (!isset($field_definitions[$field_name])) {
-      throw new NotFoundHttpException(sprintf('Field "%s" does not exist.', $field_name));
-    }
-
-    /** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
-    $field_definition = $field_definitions[$field_name];
-    if ($field_definition->getSetting('target_type') !== 'file') {
-      throw new AccessDeniedException(sprintf('"%s" is not a file field', $field_name));
-    }
-
-    return $field_definition;
-  }
-
 }
diff --git a/core/modules/jsonapi/tests/src/Functional/EntryPointTest.php b/core/modules/jsonapi/tests/src/Functional/EntryPointTest.php
index 22f5da27cc..815bee4e6f 100644
--- a/core/modules/jsonapi/tests/src/Functional/EntryPointTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/EntryPointTest.php
@@ -27,6 +27,7 @@ class EntryPointTest extends BrowserTestBase {
     'node',
     'jsonapi',
     'basic_auth',
+    'file',
   ];
 
   /**
diff --git a/core/modules/jsonapi/tests/src/Functional/ExternalNormalizersTest.php b/core/modules/jsonapi/tests/src/Functional/ExternalNormalizersTest.php
index 3214ca4301..a0af7578a8 100644
--- a/core/modules/jsonapi/tests/src/Functional/ExternalNormalizersTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/ExternalNormalizersTest.php
@@ -47,6 +47,7 @@ class ExternalNormalizersTest extends BrowserTestBase {
   protected static $modules = [
     'jsonapi',
     'entity_test',
+    'file',
   ];
 
   /**
diff --git a/core/modules/jsonapi/tests/src/Functional/InternalEntitiesTest.php b/core/modules/jsonapi/tests/src/Functional/InternalEntitiesTest.php
index 032c49698e..f43d607466 100644
--- a/core/modules/jsonapi/tests/src/Functional/InternalEntitiesTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/InternalEntitiesTest.php
@@ -28,6 +28,7 @@ class InternalEntitiesTest extends BrowserTestBase {
     'jsonapi',
     'entity_test',
     'serialization',
+    'file',
   ];
 
   /**
diff --git a/core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php b/core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php
index d7dae50d0c..01aae8e0dc 100644
--- a/core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php
+++ b/core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php
@@ -65,6 +65,7 @@ abstract class ResourceTestBase extends BrowserTestBase {
     'rest_test',
     'jsonapi_test_field_access',
     'text',
+    'file',
   ];
 
   /**
diff --git a/core/modules/jsonapi/tests/src/Functional/RestJsonApiUnsupported.php b/core/modules/jsonapi/tests/src/Functional/RestJsonApiUnsupported.php
index e0a79f3f56..90722f8ede 100644
--- a/core/modules/jsonapi/tests/src/Functional/RestJsonApiUnsupported.php
+++ b/core/modules/jsonapi/tests/src/Functional/RestJsonApiUnsupported.php
@@ -22,7 +22,7 @@ class RestJsonApiUnsupported extends ResourceTestBase {
   /**
    * {@inheritdoc}
    */
-  protected static $modules = ['jsonapi', 'node'];
+  protected static $modules = ['jsonapi', 'node', 'file'];
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/jsonapi/tests/src/Kernel/Context/FieldResolverTest.php b/core/modules/jsonapi/tests/src/Kernel/Context/FieldResolverTest.php
index fc274647fa..9cddbe0db0 100644
--- a/core/modules/jsonapi/tests/src/Kernel/Context/FieldResolverTest.php
+++ b/core/modules/jsonapi/tests/src/Kernel/Context/FieldResolverTest.php
@@ -24,6 +24,7 @@ class FieldResolverTest extends JsonapiKernelTestBase {
     'field',
     'text',
     'user',
+    'file',
   ];
 
   /**
diff --git a/core/modules/jsonapi/tests/src/Kernel/Controller/EntityResourceTest.php b/core/modules/jsonapi/tests/src/Kernel/Controller/EntityResourceTest.php
index 859cb80599..ec56dd9f29 100644
--- a/core/modules/jsonapi/tests/src/Kernel/Controller/EntityResourceTest.php
+++ b/core/modules/jsonapi/tests/src/Kernel/Controller/EntityResourceTest.php
@@ -46,6 +46,7 @@ class EntityResourceTest extends JsonapiKernelTestBase {
     'serialization',
     'system',
     'user',
+    'file',
   ];
 
   /**
diff --git a/core/modules/jsonapi/tests/src/Kernel/EventSubscriber/ResourceObjectNormalizerCacherTest.php b/core/modules/jsonapi/tests/src/Kernel/EventSubscriber/ResourceObjectNormalizerCacherTest.php
index e715edc47e..67937d79b1 100644
--- a/core/modules/jsonapi/tests/src/Kernel/EventSubscriber/ResourceObjectNormalizerCacherTest.php
+++ b/core/modules/jsonapi/tests/src/Kernel/EventSubscriber/ResourceObjectNormalizerCacherTest.php
@@ -27,6 +27,7 @@ class ResourceObjectNormalizerCacherTest extends KernelTestBase {
     'serialization',
     'jsonapi',
     'user',
+    'file',
   ];
 
   /**
diff --git a/core/modules/jsonapi/tests/src/Kernel/Normalizer/FieldItemNormalizerTest.php b/core/modules/jsonapi/tests/src/Kernel/Normalizer/FieldItemNormalizerTest.php
index cb40e712b5..f290f971db 100644
--- a/core/modules/jsonapi/tests/src/Kernel/Normalizer/FieldItemNormalizerTest.php
+++ b/core/modules/jsonapi/tests/src/Kernel/Normalizer/FieldItemNormalizerTest.php
@@ -26,6 +26,7 @@ class FieldItemNormalizerTest extends JsonapiKernelTestBase {
     'link',
     'entity_test',
     'serialization',
+    'file',
   ];
 
   /**
diff --git a/core/modules/jsonapi/tests/src/Kernel/Normalizer/LinkCollectionNormalizerTest.php b/core/modules/jsonapi/tests/src/Kernel/Normalizer/LinkCollectionNormalizerTest.php
index 4282b62947..8dc4cb8ebb 100644
--- a/core/modules/jsonapi/tests/src/Kernel/Normalizer/LinkCollectionNormalizerTest.php
+++ b/core/modules/jsonapi/tests/src/Kernel/Normalizer/LinkCollectionNormalizerTest.php
@@ -53,6 +53,7 @@ class LinkCollectionNormalizerTest extends KernelTestBase {
     'serialization',
     'system',
     'user',
+    'file',
   ];
 
   /**
diff --git a/core/modules/jsonapi/tests/src/Kernel/ResourceType/RelatedResourceTypesTest.php b/core/modules/jsonapi/tests/src/Kernel/ResourceType/RelatedResourceTypesTest.php
index 3ee33a9c6e..f6d56d70a9 100644
--- a/core/modules/jsonapi/tests/src/Kernel/ResourceType/RelatedResourceTypesTest.php
+++ b/core/modules/jsonapi/tests/src/Kernel/ResourceType/RelatedResourceTypesTest.php
@@ -25,6 +25,7 @@ class RelatedResourceTypesTest extends JsonapiKernelTestBase {
     'system',
     'user',
     'field',
+    'file',
   ];
 
   /**
diff --git a/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTest.php b/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTest.php
index 5503967191..41acfe54b9 100644
--- a/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTest.php
+++ b/core/modules/jsonapi/tests/src/Kernel/ResourceType/ResourceTypeRepositoryTest.php
@@ -25,6 +25,7 @@ class ResourceTypeRepositoryTest extends JsonapiKernelTestBase {
     'system',
     'user',
     'jsonapi_test_resource_type_building',
+    'file',
   ];
 
   /**
diff --git a/core/modules/jsonapi/tests/src/Kernel/Revisions/VersionNegotiatorTest.php b/core/modules/jsonapi/tests/src/Kernel/Revisions/VersionNegotiatorTest.php
index ce8fabf3df..eb87fc1dfe 100644
--- a/core/modules/jsonapi/tests/src/Kernel/Revisions/VersionNegotiatorTest.php
+++ b/core/modules/jsonapi/tests/src/Kernel/Revisions/VersionNegotiatorTest.php
@@ -64,6 +64,7 @@ class VersionNegotiatorTest extends JsonapiKernelTestBase {
     'serialization',
     'system',
     'user',
+    'file',
   ];
 
   /**
diff --git a/core/modules/jsonapi/tests/src/Kernel/Serializer/SerializerTest.php b/core/modules/jsonapi/tests/src/Kernel/Serializer/SerializerTest.php
index b55b25b98f..54cbc83fd9 100644
--- a/core/modules/jsonapi/tests/src/Kernel/Serializer/SerializerTest.php
+++ b/core/modules/jsonapi/tests/src/Kernel/Serializer/SerializerTest.php
@@ -33,6 +33,7 @@ class SerializerTest extends JsonapiKernelTestBase {
     'text',
     'filter',
     'jsonapi_test_data_type',
+    'file',
   ];
 
   /**
diff --git a/core/tests/Drupal/KernelTests/Core/Extension/ModuleConfigureRouteTest.php b/core/tests/Drupal/KernelTests/Core/Extension/ModuleConfigureRouteTest.php
index e81a11de31..0d3609d931 100644
--- a/core/tests/Drupal/KernelTests/Core/Extension/ModuleConfigureRouteTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Extension/ModuleConfigureRouteTest.php
@@ -18,7 +18,7 @@ class ModuleConfigureRouteTest extends KernelTestBase {
   /**
    * {@inheritdoc}
    */
-  protected static $modules = ['system', 'user', 'path_alias'];
+  protected static $modules = ['system', 'user', 'path_alias', 'file'];
 
   /**
    * @var \Drupal\Core\Routing\RouteProviderInterface
