Skip to content

ModelAttributeMethodArgumentResolver should expose validateIfApplicable and isBindExceptionRequired as protected extension points #36690

@husseinvr97

Description

@husseinvr97

Describe the use case

When migrating from Spring MVC to WebFlux, a common customization pattern is to subclass ModelAttributeMethodProcessor and override validateIfApplicable to apply group-based or context-aware validation — for example, selecting different @Validated groups based on the HTTP method, tenant context, or feature flags. The same need exists in WebFlux, but ModelAttributeMethodArgumentResolver does not support it.

Similarly, isBindExceptionRequired is overridden in MVC subclasses to control whether a binding/validation failure should throw or be silently passed to a BindingResult parameter — this decision sometimes needs to depend on runtime state, not just parameter inspection.

What is missing

ModelAttributeMethodProcessor (MVC) exposes both as protected extension points:

protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter)
protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter)
protected boolean isBindExceptionRequired(MethodParameter parameter)

In ModelAttributeMethodArgumentResolver (WebFlux), validateIfApplicable is private and isBindExceptionRequired does not exist at all — the equivalent logic is inlined as a private hasErrorsArgument helper. Neither can be overridden by subclasses.

Proposed solution

Change validateIfApplicable from private to protected:

protected void validateIfApplicable(WebExchangeDataBinder binder,
                                     MethodParameter parameter,
                                     ServerWebExchange exchange)

Introduce isBindExceptionRequired as a protected method:

protected boolean isBindExceptionRequired(MethodParameter parameter)

The existing private hasErrorsArgument(MethodParameter) can be replaced by this method with no behavioral change.

Precedent in the same class

PR #24947 already established this pattern by making bindRequestParameters protected in ModelAttributeMethodArgumentResolver. The class currently exposes two protected reactive extension points:

protected Mono<Void> constructAttribute(WebExchangeDataBinder binder, ServerWebExchange exchange)
protected Mono<Void> bindRequestParameters(WebExchangeDataBinder binder, ServerWebExchange exchange)

validateIfApplicable sits at the same level in the binding lifecycle and should follow the same convention. The visibility change for validateIfApplicable keeps the synchronous signature consistent with how it is called internally — inside a .doOnSuccess() — which matches the existing contract. A separate reactive variant can be considered later if async validation becomes a requirement.
No behavioral change
Both changes are purely additive. Existing behavior is unaffected for any code that does not subclass ModelAttributeMethodArgumentResolver.

Happy to open a PR about it if the proposed solution is fine and also if there is any other approach you may want to have for this case I will be happy to help

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions