Custom 'required' field message ignored for file uploads
The error message for managed_file fields is added by  ManagedFile::validateManagedFile - it does not support #required_error - the 'file missing' message (and others) are hardcoded:

  $form_state
      -&gt;setError($element, t('@name field is required.', [
      '@name' =&gt; $element['#title'],
    ]));


This is presumably because of the multiple types of error possible:


file does not exist
file is "not referenced" (it exists but is "unmanaged" - i.e. orphaned as far as DB concerned - see function docs linked above)
or, in our case, a file has not been uploaded at all


Workaround:

I bypassed this by adding my own validation to the form.

The Webform module has hook_webform_submission_form_alter - you can use this to append your own validation method:

function mymodule_webform_submission_form_alter(&amp;$form, FormStateInterface $form_state, $form_id) {
    if ( $form['#webform_id'] != 'application' ) {
        // only do this for the specified form
        return;
    }

    $form['#validate'][] = 'mymodule_fix_webform_validation';
}

function mymodule_fix_webform_validation($form, &amp;$form_state) {

    // grab all the existing errors
    $errors = $form_state-&gt;getErrors();

    if ( isset( $errors['cv'] ) &amp;&amp;
         $errors['cv']-&gt;getUntranslatedString() == '@name field is required.' ) {
         // if (a) there's an error for this field, and
         // (b) it's the correct TYPE of error

         // reconstruct the error message
         // (you might add translatable strings if you need them)
         $errors['cv']-&gt;__construct( 'Please upload a copy of your CV.' );
    }

}


(Decided to use the constructor from reading this answer to a similar question.)

Things I tried first:

The standard approach is to use $form_state->setError() or setErrorByName() - but neither appeared to have any effect on the error array - don't understand why.  

On the actual form, the field was nested in a Webform "section"…

$form['elements']['uploads_section']['cv']


…so assumed I should supply the full array to setError(), for it to pull out the value of #parents and use that to construct the key.  Except the error is actually stored as a flat 'cv' key, without the parent.  Trying to set the 'cv' error using ByName didn't work either.

Overriding strings:

An alternative would be override the string (at time of writing, needs to be done programmatically: the string overrides module is not yet ready for D8). Add the following to settings.php:

$settings['locale_custom_strings_en'][''] = array(
    '@name field is required.' =&gt; 'Hello world!',
);


But this is a very broad brush and will apply to every file input on every form (plus anywhere else the phrase "@name field is required." is used - you can't specify the @name placeholder).