Skip to content

Api version qfix#1765

Open
BoykoAlex wants to merge 3 commits intomainfrom
api-version-qfix
Open

Api version qfix#1765
BoykoAlex wants to merge 3 commits intomainfrom
api-version-qfix

Conversation

@BoykoAlex
Copy link
Contributor

Fixes #1659

@martinlippert
Copy link
Member

Tried this, looks really awesome. Great work.

One minor polish could be to change the label of the quick fixes to be a little more precise and self-explaining. For example instead of:

Bean: WebMVC API versioning via path segment

We call it:

Create Web Config Bean with WebMVC versioning via path segment

In case a web config already exists, it should instead be something like:

Add WebMVC versioning via path segment to <name of web config class>

And similarly for properties, so that users know more precisely what will happen when they execute the quick fix... 😀

@BoykoAlex BoykoAlex force-pushed the api-version-qfix branch 2 times, most recently from a11ff60 to 03dd474 Compare February 5, 2026 01:03
@BoykoAlex
Copy link
Contributor Author

I've corrected the labels. Should be done from my perspective. Still rewrite recipe for the quickfix for this one.

@martinlippert
Copy link
Member

I tried the latest changes from the branch and the quick fixes for the web config beans are somehow broken. Invoking them throws this on the language server side:

09:44:26.582 ERROR [ForkJoinPool.commonPool-worker-1] o.s.i.v.b.j.rewrite.RewriteRefactorings  : 
java.lang.NoSuchFieldException: filePath
	at java.base/java.lang.Class.getDeclaredField(Class.java:2382)
	at org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings.convertRecipeParameters(RewriteRefactorings.java:215)
	at org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings.lambda$13(RewriteRefactorings.java:174)
	at java.base/java.util.Optional.map(Optional.java:260)
	at org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings.lambda$12(RewriteRefactorings.java:174)
	at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1171)
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:531)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1794)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1781)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:511)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1450)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2019)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:187)
09:44:26.582 ERROR [ForkJoinPool.commonPool-worker-1] o.s.i.v.b.j.rewrite.RewriteRefactorings  : 
java.lang.NoSuchFieldException: isFlux
	at java.base/java.lang.Class.getDeclaredField(Class.java:2382)
	at org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings.convertRecipeParameters(RewriteRefactorings.java:215)
	at org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings.lambda$13(RewriteRefactorings.java:174)
	at java.base/java.util.Optional.map(Optional.java:260)
	at org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings.lambda$12(RewriteRefactorings.java:174)
	at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1171)
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:531)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1794)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1781)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:511)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1450)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2019)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:187)
09:44:26.583 ERROR [ForkJoinPool.commonPool-worker-1] o.s.i.v.b.j.rewrite.RewriteRefactorings  : 
java.lang.NoSuchFieldException: configType
	at java.base/java.lang.Class.getDeclaredField(Class.java:2382)
	at org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings.convertRecipeParameters(RewriteRefactorings.java:215)
	at org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings.lambda$13(RewriteRefactorings.java:174)
	at java.base/java.util.Optional.map(Optional.java:260)
	at org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings.lambda$12(RewriteRefactorings.java:174)
	at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1171)
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:531)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1794)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1781)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:511)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1450)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2019)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:187)
09:44:26.583 ERROR [ForkJoinPool.commonPool-worker-1] o.s.i.v.b.j.rewrite.RewriteRefactorings  : 
java.lang.NoSuchFieldException: value
	at java.base/java.lang.Class.getDeclaredField(Class.java:2382)
	at org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings.convertRecipeParameters(RewriteRefactorings.java:215)
	at org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings.lambda$13(RewriteRefactorings.java:174)
	at java.base/java.util.Optional.map(Optional.java:260)
	at org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings.lambda$12(RewriteRefactorings.java:174)
	at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1171)
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:531)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1794)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1781)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:511)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1450)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2019)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:187)
09:44:26.583 ERROR [ForkJoinPool.commonPool-worker-1] o.s.i.v.b.j.rewrite.RewriteRefactorings  : 
java.lang.NoSuchFieldException: pkgName
	at java.base/java.lang.Class.getDeclaredField(Class.java:2382)
	at org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings.convertRecipeParameters(RewriteRefactorings.java:215)
	at org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings.lambda$13(RewriteRefactorings.java:174)
	at java.base/java.util.Optional.map(Optional.java:260)
	at org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings.lambda$12(RewriteRefactorings.java:174)
	at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1171)
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:531)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1794)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1781)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:511)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1450)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2019)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:187)
09:44:26.584 ERROR [ForkJoinPool.commonPool-worker-1] o.eclipse.lsp4j.jsonrpc.RemoteEndpoint   : Internal error: java.lang.NullPointerException: Property must not be null: edit
java.util.concurrent.CompletionException: java.lang.NullPointerException: Property must not be null: edit
	at java.base/java.util.concurrent.CompletableFuture.wrapInCompletionException(CompletableFuture.java:323)
	at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:376)
	at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:391)
	at java.base/java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:729)
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:531)
	at java.base/java.util.concurrent.CompletableFuture.completeExceptionally(CompletableFuture.java:2221)
	at reactor.core.publisher.MonoToCompletableFuture.onError(MonoToCompletableFuture.java:78)
	at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onError(FluxOnAssembly.java:545)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:137)
	at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:540)
	at reactor.core.publisher.MonoCompletionStage$MonoCompletionStageSubscription.apply(MonoCompletionStage.java:121)
	at reactor.core.publisher.MonoCompletionStage$MonoCompletionStageSubscription.apply(MonoCompletionStage.java:67)
	at java.base/java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:955)
	at java.base/java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:932)
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:531)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1794)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1781)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:511)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1450)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2019)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:187)
Caused by: java.lang.NullPointerException: Property must not be null: edit
	at org.eclipse.lsp4j.util.Preconditions.checkNotNull(Preconditions.java:29)
	Suppressed: The stacktrace has been enhanced by Reactor, refer to additional information below: 
Assembly trace from producer [reactor.core.publisher.MonoFlatMap] :
	reactor.core.publisher.Mono.flatMap(Mono.java:3172)
	org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer.executeCommand(SimpleLanguageServer.java:276)
Error has been observed at the following site(s):
	*__Mono.flatMap ⇢ at org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer.executeCommand(SimpleLanguageServer.java:276)
Original Stack Trace:
		at org.eclipse.lsp4j.util.Preconditions.checkNotNull(Preconditions.java:29)
		at org.eclipse.lsp4j.ApplyWorkspaceEditParams.<init>(ApplyWorkspaceEditParams.java:41)
		at org.eclipse.lsp4j.ApplyWorkspaceEditParams.<init>(ApplyWorkspaceEditParams.java:45)
		at org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer.lambda$2(SimpleLanguageServer.java:277)
		at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:133)
		at reactor.core.publisher.MonoCompletionStage$MonoCompletionStageSubscription.apply(MonoCompletionStage.java:121)
		at reactor.core.publisher.MonoCompletionStage$MonoCompletionStageSubscription.apply(MonoCompletionStage.java:67)
		at java.base/java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:955)
		at java.base/java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:932)
		at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:531)
		at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1794)
		at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1781)
		at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:511)
		at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1450)
		at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2019)
		at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:187)

@BoykoAlex
Copy link
Contributor Author

BoykoAlex commented Feb 5, 2026

Hmm... that is interesting indeed... the tests pass and I specifically put in integration test to go cover the FixDescritpor -> recipe execution bit (so recipe deserialization). I'll investigate... but sad that the test didn't catch this despite all of my efforts

Signed-off-by: BoykoAlex <alex.boyko@broadcom.com>
Signed-off-by: BoykoAlex <alex.boyko@broadcom.com>
@BoykoAlex
Copy link
Contributor Author

BoykoAlex commented Feb 6, 2026

@martinlippert The inconsistency between test harness and live LS process is from different Gson instances which are differently configured. In addition to that RewriteRefactorings reads json from CodeAction#data (general purpose data placeholder in LSP) using also non LSP4J configured Gson. The difference is that lsp4j has special adapter for enum to serialize them as numbers rather than strings. Therefore diagnostic and code actions with object data fields are serialized with numbers for enums but when we look at these objects json on the LS side again to execute code action, read command params etc the enum cannot be properly loaded from json unless adapters provided which is a hack.
Therefore, changes made to:

  1. Have the same Gson instance that is used to serealize LSP messages to deserialize arbitrary json data (i.e. RewriteRefactorings
  2. Use the Lsp4j configured Gson for test harness that includes custom type adapaters injected via beans.
  3. Update lsp4j to 0.24.0 as API to get to the used Gson is available in the latest lsp4j. There was a bit of adoption changes related to lsp4j upgrade.

More replacement for new Gson() can be completed however they don't seem urgent as there are no enum usages.

Signed-off-by: BoykoAlex <alex.boyko@broadcom.com>
@martinlippert
Copy link
Member

Great findings, glad you found the root cause of this!

@martinlippert
Copy link
Member

Testing the latest version here. If I have a properties file called application-cloud.properties, but no application.properties, and choose the quick fix for the properties variant, I get:

09:29:12.354 ERROR [ForkJoinPool.commonPool-worker-1] o.eclipse.lsp4j.jsonrpc.RemoteEndpoint   : Internal error: java.lang.NullPointerException: Property must not be null: edit
java.util.concurrent.CompletionException: java.lang.NullPointerException: Property must not be null: edit
	at java.base/java.util.concurrent.CompletableFuture.wrapInCompletionException(CompletableFuture.java:323)
	at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:376)
	at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:391)
	at java.base/java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:729)
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:531)
	at java.base/java.util.concurrent.CompletableFuture.completeExceptionally(CompletableFuture.java:2221)
	at reactor.core.publisher.MonoToCompletableFuture.onError(MonoToCompletableFuture.java:78)
	at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onError(FluxOnAssembly.java:545)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:137)
	at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:540)
	at reactor.core.publisher.MonoCompletionStage$MonoCompletionStageSubscription.apply(MonoCompletionStage.java:121)
	at reactor.core.publisher.MonoCompletionStage$MonoCompletionStageSubscription.apply(MonoCompletionStage.java:67)
	at java.base/java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:955)
	at java.base/java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:932)
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:531)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1794)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1781)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:511)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1450)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2019)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:187)
Caused by: java.lang.NullPointerException: Property must not be null: edit
	at org.eclipse.lsp4j.jsonrpc.util.Preconditions.checkNotNull(Preconditions.java:29)
	Suppressed: The stacktrace has been enhanced by Reactor, refer to additional information below: 
Assembly trace from producer [reactor.core.publisher.MonoFlatMap] :
	reactor.core.publisher.Mono.flatMap(Mono.java:3172)
	org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer.executeCommand(SimpleLanguageServer.java:281)
Error has been observed at the following site(s):
	*__Mono.flatMap ⇢ at org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer.executeCommand(SimpleLanguageServer.java:281)
Original Stack Trace:
		at org.eclipse.lsp4j.jsonrpc.util.Preconditions.checkNotNull(Preconditions.java:29)
		at org.eclipse.lsp4j.ApplyWorkspaceEditParams.<init>(ApplyWorkspaceEditParams.java:40)
		at org.eclipse.lsp4j.ApplyWorkspaceEditParams.<init>(ApplyWorkspaceEditParams.java:44)
		at org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer.lambda$2(SimpleLanguageServer.java:282)
		at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:133)
		at reactor.core.publisher.MonoCompletionStage$MonoCompletionStageSubscription.apply(MonoCompletionStage.java:121)
		at reactor.core.publisher.MonoCompletionStage$MonoCompletionStageSubscription.apply(MonoCompletionStage.java:67)
		at java.base/java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:955)
		at java.base/java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:932)
		at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:531)
		at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1794)
		at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1781)
		at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:511)
		at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1450)
		at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2019)
		at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:187)

@martinlippert
Copy link
Member

There is also the question of whether we want the property quick fix variant working even if there is no properties file at all, similar to there is no web config class, so that the quick fix would create an application.properties file. Probably an edge case, but it would look more well aligned with the other variant.

@martinlippert
Copy link
Member

I also see issues with deleting or creating files not causing the overall validation to be re-run to update the quick fixes accordingly, but I would track and tackle that in a separate issue once we have this one in. I can then look into the polish for when validations are re-triggered later.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[framework 7] API versioning - quick fix for adding version configuration

2 participants