Skip to content

Conversation

@JKaplanEmpty-Nes
Copy link
Contributor

The current WebFlux API resolution returned a nullable String, this prevented custom API resolvers to be based on values which would need to be blocked.
For example, creating an API version resolver based on the Authorized Principal. Since this is returned as a Mono in a server exchange.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Dec 30, 2025
@JKaplanEmpty-Nes JKaplanEmpty-Nes force-pushed the main branch 4 times, most recently from 7a312d0 to 396a634 Compare December 30, 2025 18:18
@JKaplanEmpty-Nes JKaplanEmpty-Nes changed the title Change the API version resolution from being a nullable String to a Mono String Change the WebFlux API version resolution from being a nullable String to a Mono String Dec 30, 2025
@bclozel
Copy link
Member

bclozel commented Dec 30, 2025

It is unfortunately too late for this type of change. This would break existing public contracts in a spectacular fashion. I will leave this open for now so we can discuss the use case.

@bclozel bclozel added the in: web Issues in web modules (web, webmvc, webflux, websocket) label Dec 30, 2025
@JKaplanEmpty-Nes
Copy link
Contributor Author

It is unfortunately too late for this type of change. This would break existing public contracts in a spectacular fashion. I will leave this open for now so we can discuss the use case.

What if I changed the public contracts so that the old return was there, but added a new reactive one and by default it would enclose the old result in a mono?

@bclozel
Copy link
Member

bclozel commented Dec 30, 2025

We can always consider solutions like that. Let us discuss this matter first.

@JKaplanEmpty-Nes JKaplanEmpty-Nes force-pushed the main branch 2 times, most recently from 1a29790 to 40f7f4c Compare December 30, 2025 19:57
@JKaplanEmpty-Nes
Copy link
Contributor Author

JKaplanEmpty-Nes commented Dec 30, 2025

We can always consider solutions like that. Let us discuss this matter first.

Absolutely I would love to discuss my use case, just in case I made the change to not break the previous contracts.

The specific case is that we set the version (alpha, beta, GA) based on the Authorized Principal of the request.
However using WebFlux we get that wrapped in a Mono, which we obviously can't block to resolve.

Copy link
Contributor

@rstoyanchev rstoyanchev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was unfortunately an oversight, but we need to consider the best way to get there with the least disruption.

I do like the idea of allowing an ApiVersionResolver to be sync or async, so the resolveVersionReactively method with a default implementation is good, but I would call it resolveVersionAsync.

However, it doesn't make sense to keep both sync and async variants at the level of ApiVersionStrategy, so we should deprecate the sync version for removal, and again call the new method resolveVersionAsync. A similar pattern for resolveParseAndValidate.

During the deprecation phase, we do need to continue to call the sync resolveParseAndValidate first, suppressing any MissingApiVersionException, and then conditionally call the new async variant if the sync version did not produce a version.

Let me know if you plan to make those updates.

@rstoyanchev rstoyanchev self-assigned this Jan 5, 2026
@rstoyanchev rstoyanchev added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Jan 5, 2026
@rstoyanchev rstoyanchev added this to the 7.0.3 milestone Jan 5, 2026
@rstoyanchev rstoyanchev changed the title Change the WebFlux API version resolution from being a nullable String to a Mono String Allow WebFlux ApiVersionResolver to return a Mono Jan 5, 2026
@JKaplanEmpty-Nes JKaplanEmpty-Nes force-pushed the main branch 3 times, most recently from 1947510 to e0776f9 Compare January 5, 2026 18:25
@JKaplanEmpty-Nes
Copy link
Contributor Author

JKaplanEmpty-Nes commented Jan 5, 2026

I pushed the following changes:
ApiVersionResolver.resolveVersionReactively renamed to resolveVersionAsync
ApiVersionStrategy.resolveVersionReactively renamed to resolveVersionAsync

Deprecated ApiVersionStrategy.resolveVersion since 7.0.3

Instead of defaulting the async resolver to sync resolver in the ApiVersionStrategy, both methods are being called when calling resolveParseAndValidate. If the sync method throws a MissingApiVersionException then it is suppressed.

@rstoyanchev
Copy link
Contributor

@JKaplanEmpty-Nes, thinking further about the use case:

The specific case is that we set the version (alpha, beta, GA) based on the Authorized Principal of the request.

Could you clarify why the version depends on the logged in user? It seems to me the version to use should match the one requested by the client, which knows best what their capability and compatibility level is.

@JKaplanEmpty-Nes
Copy link
Contributor Author

@JKaplanEmpty-Nes, thinking further about the use case:

The specific case is that we set the version (alpha, beta, GA) based on the Authorized Principal of the request.

Could you clarify why the version depends on the logged in user? It seems to me the version to use should match the one requested by the client, which knows best what their capability and compatibility level is.

Sure, I can go into a bit more detail. My use case is very specific, but allowing async API version resolution will solve for it and other possible cases.

We use a 3rd party service called LaunchDarkly. It allows us to select the correct version of an API based on a supplied context. I created a Launch Darkly API Version resolver for WebMvc just fine however I was unable to do it for WebFlux. The context we send to Launch Darkly includes information from the JWT.

This allows us to switch versions for specific segments of users and allows us to change the routing at any time.

@rstoyanchev
Copy link
Contributor

Thanks for elaborating.

Signed-off-by: Jonathan Kaplan <jkaplan@empty-nes.com>
@rstoyanchev
Copy link
Contributor

@JKaplanEmpty-Nes as I mentioned before, I'm already making changes locally. Please, don't push any more changes. All of those will not be included.

@JKaplanEmpty-Nes
Copy link
Contributor Author

@JKaplanEmpty-Nes as I mentioned before, I'm already making changes locally. Please, don't push any more changes. All of those will not be included.

Sure, I misunderstood. I thought you said you were processing it locally not updating the code.

rstoyanchev pushed a commit that referenced this pull request Jan 8, 2026
See gh-36084

Signed-off-by: Jonathan Kaplan <jkaplan@empty-nes.com>
rstoyanchev added a commit that referenced this pull request Jan 8, 2026
rstoyanchev added a commit that referenced this pull request Jan 8, 2026
rstoyanchev added a commit that referenced this pull request Jan 8, 2026
- deprecate sync method on ApiVersionResolver
- add SyncApiVersionResolver
- refactor resolverParseAndValidateApiVersion method

See gh-36084
rstoyanchev added a commit that referenced this pull request Jan 8, 2026
Resolve conflicts and refactor merged code in AbstractHandlerMapping

See gh-36084
@rstoyanchev
Copy link
Contributor

rstoyanchev commented Jan 8, 2026

Yeah, processing as in processing the PR, not digesting it.

I've merged the changes and also evolved the solution a bit further. The main thing to point out are the changes in ApiVersionResolver, deprecating the sync method and making the async method the main one instead (for @FunctionalInterface), and creating an SyncApiVersionResolver extension. This is a better setup since otherwise if you implement the async method, the sync one remains without an obvious implementation. It also matches a pattern we've used elsewhere in WebFlux. This does break lambda implementations, but that's inevitable with the new setup after the deprecation phase, so it's better to absorb that now. In any case the expectation is for concrete implementations so I think this should have limited impact at this point in time early in the 7.0.x phase.

Hopefully the rest is self explanatory and makes sense, but feel free to comment.

@rstoyanchev rstoyanchev closed this Jan 8, 2026
@JKaplanEmpty-Nes
Copy link
Contributor Author

Yeah, processing as in processing the PR, not digesting it.

I've merged the changes and also evolved the solution a bit further. The main thing to point out are the changes in ApiVersionResolver, deprecating the sync method and making the async method the main one instead (for @FunctionalInterface), and creating an SyncApiVersionResolver extension. This is a better setup since otherwise if you implement the async method, the sync one remains without an obvious implementation. It also matches a pattern we've used elsewhere in WebFlux. This does break lambda implementations, but that's inevitable with the new setup after the deprecation phase, so it's better to absorb that now. In any case the expectation is for concrete implementations so I think this should have limited impact at this point in time early in the 7.0.x phase.

Hopefully the rest is self explanatory and makes sense, but feel free to comment.

As long as we have an async resolver then I am absolutely delighted. My first commit also changed the defaults to the asynced version but I changed it to the synced version based on the first suggestion.

rstoyanchev added a commit that referenced this pull request Jan 9, 2026
Instead of making it async and having a sync subinterface variant,
this restores ApiVersionResolver to be as it was with an async
subinterface variant.

ApiVersionStrategy, and the infrastructure invoking it, remains
async first, but also accommodates sync resolvers.

This should provide a better balance with backwards compatibility
while also accommodating async version resolution as the less
common scenario.

See gh-36084
@rstoyanchev
Copy link
Contributor

rstoyanchev commented Jan 9, 2026

I've done one more iteration, restoring ApiVersionResolver to be exactly the way it was, and having AsyncApiVersionResolver as the sub-interface that can be implemented when necessary. In effect, the overall version resolution is still async first, but the sync resolver contract remains front and center as the more common way to resolve a version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants