The issue is FileEntry.validateResults(FacesContext, FileEntryResults) line 238 that calls renderResponse().
if (failed)
{
facesContext.validationFailed();
facesContext.renderResponse();
}
Basically, this always is called in decode, so if the upload fails and required="true" on the fileEntry, then the validation phase will never run for the other components, unless they have immediate="true" as well.
With other input components, their validation and their valueChangeListener are controlled by their immediate property, whereas with fileEntry, the validation seems fixed and only the fileEntryListener seems to be affected by immediate. But it calling renderResponse() is consistent with what they all do.
We'll need to choose our fix, either we make fileEntry with immediate="false" do validation in validation phase, which might be more correct but might also break some backwards compatibility, or we introduce some other means of specifying when validation happens. That could be a boolean immediateValidation with default value of true, or it could be a String/enum property that takes a phase, such as decode and validate. On it's own I'd just do the boolean, but if ICE-8225 used a phase, then it'd make sense to do the same here, for consistency.
<ace:fileEntry validationPhase="VALIDATION" eventPhase="RENDER_RESPONSE" .../>
<ace:fileEntry validationImmediate="false" eventPhase="RENDER_RESPONSE" .../>
Either way, no it does not make sense for the fileEntry to have not uploaded the file based on some other component state, or really any server state, since the upload happens before the lifecycle even runs. The model we have is that the fileEntryListener is invoked, and it can see that there was a successful file upload, but that the rest of the form failed validation, and then act accordingly, to still keep the file or to delete it itself.
Attached test case that shows the issue. Load fileUploadProblem1.jsf to see this issue.