diff --git a/core/lib/Drupal/Component/Utility/BytesValidator.php b/core/lib/Drupal/Component/Utility/BytesValidator.php
new file mode 100644
index 0000000000..417f76792e
--- /dev/null
+++ b/core/lib/Drupal/Component/Utility/BytesValidator.php
@@ -0,0 +1,129 @@
+<?php
+
+namespace Drupal\Component\Utility;
+
+/**
+ * Provides helper methods for max file size value validation.
+ *
+ * @ingroup utility
+ */
+class BytesValidator {
+
+  /**
+   * Holds a string representing a byte size to be validated.
+   *
+   * @var string
+   *   A string size expressed as a number of bytes with optional SI
+   *   or IEC binary unit prefix (e.g. 2, 3K, 5MB, 10G, 6GiB, 8 bytes, 9mbytes).
+   */
+  private static $size;
+
+  /**
+   * Form API callback.
+   *
+   * Ensures that a size has been entered and that it can be parsed by
+   * \Drupal\Component\Utility\Bytes::toInt().
+   *
+   * @param string $byte_size_string
+   *   A user entered string representing a byte size to be validated.
+   *
+   * @return bool
+   *   TRUE if meaningful byte size string, FALSE if not.
+   */
+  public static function validateByteSizeString($byte_size_string) {
+
+    // Spaces are irrelevant to us. Bytes::toInt will process with or without
+    // them. Make our lives easier by just doing away with them.
+    self::$size = self::stripWhitespaceFromString($byte_size_string);
+
+    // Having removed whitespace, if our string is now empty then the user
+    // either entered nothing or a string of whitespace. Accept both cases as
+    // valid.
+    if (empty(self::$size)) {
+      return TRUE;
+    }
+
+    // Theoretically self::stringIsValidByteString() should perform strict
+    // enough validation. Passing the string to Bytes::toInt() for added
+    // precaution.
+    if (!self::sizeIsValidByteString() || !self::toIntCanParseString()) {
+      return FALSE;
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * Validates the given string looks like a byte size string.
+   *
+   * @return bool
+   *   TRUE if size is a valid byte size string, FALSE if not.
+   */
+  private static function sizeIsValidByteString() {
+    return !(
+      self::sizeIsNotAlphaNumeric() ||
+      self::sizeNotStartWithNumber() ||
+      self::sizeHasNumbersFollowingAlphaChar()
+    );
+  }
+
+  /**
+   * Strips whitespace from a string of text.
+   *
+   * @param string $string
+   *   A string representing a size (5mb, 500GB etc)
+   *
+   * @return string
+   *   Returns the inputted string with spaces removed.
+   */
+  private static function stripWhitespaceFromString($string) {
+    return trim(preg_replace('/\s+/', '', $string));
+  }
+
+  /**
+   * Determines that a string does not start with a number.
+   *
+   * @return bool
+   *   TRUE if the string does not start with a numeric char, FALSE otherwise.
+   */
+  private static function sizeNotStartWithNumber() {
+    return !preg_match('/^\d/', self::$size);
+  }
+
+  /**
+   * Determines if a string has numberic chars following after alpha chars.
+   *
+   * @return bool
+   *   TRUE if the string has numeric values following after alpha chars.
+   */
+  private static function sizeHasNumbersFollowingAlphaChar() {
+    return (bool) preg_match('/[a-zA-Z]+(\d+)/', self::$size);
+  }
+
+  /**
+   * Determines if a string contains non alphanumeric characters.
+   *
+   * @return bool
+   *   Returns TRUE if non alphanumeric characters are found.
+   */
+  private static function sizeIsNotAlphaNumeric() {
+    return (bool) !preg_match('/^[a-zA-Z0-9]+$/', self::$size);
+  }
+
+  /**
+   * Determines if Bytes:toInt() is able to parse the given string.
+   *
+   * @return bool
+   *   TRUE if string can be parsed by Bytes::toInt(), FALSE otherwise.
+   */
+  private static function toIntCanParseString() {
+    try {
+      Bytes::toInt(self::$size);
+    }
+    catch (\Exception $e) {
+      return FALSE;
+    }
+    return TRUE;
+  }
+
+}
diff --git a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
index 8dd882de02..8f6849594a 100644
--- a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
+++ b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\file\Plugin\Field\FieldType;
 
+use Drupal\Component\FileSystem\BytesValidator;
 use Drupal\Component\Utility\Bytes;
 use Drupal\Component\Render\PlainTextOutput;
 use Drupal\Component\Utility\Environment;
@@ -12,6 +13,7 @@
 use Drupal\Core\File\FileSystemInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\StreamWrapper\StreamWrapperInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\Core\TypedData\DataDefinition;
 
 /**
@@ -181,7 +183,7 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
       '#default_value' => $settings['max_filesize'],
       '#description' => t('Enter a value like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes) in order to restrict the allowed file size. If left empty the file sizes will be limited only by PHP\'s maximum post and file upload sizes (current limit <strong>%limit</strong>).', ['%limit' => format_size(Environment::getUploadMaxSize())]),
       '#size' => 10,
-      '#element_validate' => [[get_class($this), 'validateMaxFilesize']],
+      '#element_validate' => [[get_class($this), 'validateExtensions']],
       '#weight' => 5,
     ];
 
@@ -197,11 +199,11 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
   }
 
   /**
-   * Form API callback
+   * Form API callback.
    *
    * Removes slashes from the beginning and end of the destination value and
-   * ensures that the file directory path is not included at the beginning of the
-   * value.
+   * ensures that the file directory path is not included at the beginning of
+   * the value.
    *
    * This function is assigned as an #element_validate callback in
    * fieldSettingsForm().
@@ -220,7 +222,8 @@ public static function validateDirectory($element, FormStateInterface $form_stat
    *
    * This doubles as a convenience clean-up function and a validation routine.
    * Commas are allowed by the end-user, but ultimately the value will be stored
-   * as a space-separated list for compatibility with file_validate_extensions().
+   * as a space-separated list for compatibility with
+   * file_validate_extensions().
    */
   public static function validateExtensions($element, FormStateInterface $form_state) {
     if (!empty($element['#value'])) {
@@ -239,15 +242,16 @@ public static function validateExtensions($element, FormStateInterface $form_sta
   /**
    * Form API callback.
    *
-   * Ensures that a size has been entered and that it can be parsed by
+   * Ensures that a valid size has been entered and that it can be parsed by
    * \Drupal\Component\Utility\Bytes::toInt().
    *
    * This function is assigned as an #element_validate callback in
    * fieldSettingsForm().
    */
   public static function validateMaxFilesize($element, FormStateInterface $form_state) {
-    if (!empty($element['#value']) && !is_numeric(Bytes::toInt($element['#value']))) {
-      $form_state->setError($element, t('The "@name" option must contain a valid value. You may either leave the text field empty or enter a string like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes).', ['@name' => $element['title']]));
+    if (BytesValidator::validateByteSizeString()) {
+      $translatable_markup = new TranslatableMarkup('The "@name" option must contain a valid value. You may either leave the text field empty or enter a string like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes).', ['@name' => $element['#title']]);
+      $form_state->setError($element, $translatable_markup);
     }
   }
 
@@ -260,10 +264,9 @@ public static function validateMaxFilesize($element, FormStateInterface $form_st
    * @return string
    *   An unsanitized file directory URI with tokens replaced. The result of
    *   the token replacement is then converted to plain text and returned.
-   *
    * @see \Drupal\Core\Utility\Token::replace()
    */
-  public function getUploadLocation($data = []) {
+  public function getUploadLocation(array $data = []) {
     return static::doGetUploadLocation($this->getSettings(), $data);
   }
 
@@ -281,7 +284,7 @@ public function getUploadLocation($data = []) {
    *
    * @see \Drupal\Core\Utility\Token::replace()
    */
-  protected static function doGetUploadLocation(array $settings, $data = []) {
+  protected static function doGetUploadLocation(array $settings, array $data = []) {
     $destination = trim($settings['file_directory'], '/');
 
     // Replace tokens. As the tokens might contain HTML we convert it to plain
diff --git a/core/modules/file/tests/src/Unit/BytesValidatorTest.php b/core/modules/file/tests/src/Unit/BytesValidatorTest.php
new file mode 100644
index 0000000000..f03b0c15a0
--- /dev/null
+++ b/core/modules/file/tests/src/Unit/BytesValidatorTest.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Drupal\Tests\file\Unit;
+
+use Drupal\Component\Utility\BytesValidator;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests FileItem's settings form's validation.
+ *
+ * @group file
+ */
+class BytesValidatorTest extends UnitTestCase {
+
+  /**
+   * Tests that a sane Max file size is entered.
+   *
+   * @dataProvider maxFileSizeInputsProvider
+   */
+  public function testValidateByteSizeString($input, $expected) {
+    $result = BytesValidator::validateByteSizeString($input);
+    $this->assertEquals($expected, $result);
+  }
+
+  /**
+   * Data provider for testValidateMaxfilesize().
+   *
+   * @return array[]
+   *   Array of inputs and error expectations.
+   */
+  public function maxFileSizeInputsProvider() {
+    $data = [
+      ['0 bytes', TRUE],
+      ['   ', TRUE],
+      ['', TRUE],
+      ['0 K', TRUE],
+      ['100bytes', TRUE],
+      ['10000', TRUE],
+      ['100 bytes', TRUE],
+      ['999989 bytes', TRUE],
+      ['999989bytes', TRUE],
+      ['2', TRUE],
+      ['3K', TRUE],
+      ['5MB', TRUE],
+      ['10G', TRUE],
+      ['6GiB', TRUE],
+      ['8bytes', TRUE],
+      ['9mbytes', TRUE],
+      // Should we allow these spaces?
+      ['1 0 0 0 0 m b y tes', TRUE],
+      [' 123456 ', TRUE],
+
+      ['123456.03 ', FALSE],
+      ['nonumbers', FALSE],
+      ['bread', FALSE],
+      ['bananas', FALSE],
+      ['1234b1', FALSE],
+      ['543xd1', FALSE],
+      ['-1', FALSE],
+      ['-975', FALSE],
+      [' word ', FALSE],
+      [' word', FALSE],
+      ['word ', FALSE],
+      [' words with spaces ', FALSE],
+      ['words with spaces ', FALSE],
+      [' words with spaces', FALSE],
+      [' 123456.03 ', FALSE],
+      ['123456.03 ', FALSE],
+      [' 123456.03', FALSE],
+      [' some words & stuff', FALSE],
+    ];
+
+    // Improve test output by using the input value as a label for the dataset.
+    foreach ($data as $index => $item) {
+      // If the index looks like an in eg "1000" "2" etc, php still detects
+      // it as a numeric index. Adding text to all the labels to force them
+      // to render.
+      $data["Sample input: '{$item[0]}'"] = $item;
+      unset($data[$index]);
+    }
+
+    return $data;
+  }
+
+}
