+The status is correctly 200 when the return type uses ResponseEntity+: ReponseEntity<Void>, Mono<ResponseEntity<Void>>, ReponseEntity<String>, Mono<ResponseEntity<String>>, ReponseEntity<MyBean>, Mono<ResponseEntity<MyBean>>, etc.
But it being null in a WebFilter after the delegation to the business method doesn't. To me it is counter-intuitive and error prone since the business method returned its result. Also note that the blocking equivalent correctly provides the response status in the filter after the business method delegation.
It's can be tricky to choose a place to set the status given that controller methods can return asynchronous values. In any case it would have to be before the return value is handled, or otherwise doing it after is too late. So we're effectively setting a default response status at a a somewhat arbitrary point in time, that is difficult to explain, and in the process creating an inconsistent way of checking for the status before vs after.
The current behavior is at least consistent in that at all times it indicates whether anything called response.setStatus or not. Essentially it's Optional.ofNullable(status).orElse(HttpStatus.OK). Question is how much value is there in knowing whether the status was explicitly set or not vs having to always check like this.
At best I can see setting the response status to HttpStatus.OK from the start and removing @Nullable from the method.
This has turned out to be a little more involved than expected, and I've also run into an unexpected issue. Now that 200 is set on the response by default, during WebSocket upgrades Tomcat and Jetty will keep the 200 instead of ignoring it and returning 101 as expected by the client. This also breaks a lot of tests that currently check for a null status. And on top of that we can't actually remove the @Nullable on getStatusCode() because of the potential for a custom integer status that is outside the range of HttpStatus enum values.
To address the issues I think we need to get rid of the statusCode field in AbstractServerHttpResponse, and instead propagate the status through to the underlying response. That way we are not forced to set a default value and on access we'll rely on whatever the default for the underlying server response is. This will also match to similar changes related to response headers, see #21783, where we now wrap the underlying native headers vs caching and applying at response commit time.
At this point however we're a little too close to 5.1.2 so I'm moving the fix version to 5.1.3.
This should be fixed in master now. The logic is more or less the same, except each server specific implementation of ServerHttpResponse#getStatusCode() falls back on whatever the default status code value is for that server, e.g. 200 for Servlet containers.