spring-projects / spring-graphql

Spring Integration for GraphQL
https://spring.io/projects/spring-graphql
Apache License 2.0
1.52k stars 300 forks source link

VS Code Run and Debug IllegalArgumentException using spring-boot-starter-graphql with Spring Boot 3.2 #955

Closed jjavery closed 5 months ago

jjavery commented 5 months ago

When running a Spring GraphQL service with Spring Boot 3.2 and VS Code's Run and Debug feature, spring-graphql throws IllegalArgumentException when requests are handled by a controller method with an @Argument annotation.

The exception is not thrown when running with VS Code's Run and Debug and Spring Boot downgraded to 3.1.11.

The exception is not thrown when running with Gradle e.g. ./gradlew bootRun and Spring Boot 3.2.

Steps to reproduce:

  1. Install VS Code with "Extension Pack for Java" and "Gradle for Java" extensions.

  2. Get the code for the Spring Guide GraphQL service in Java using Spring for GraphQL:

https://github.com/spring-guides/gs-graphql-server/tree/main

  1. Open the complete folder in VS Code

  2. Go to the Run and Debug view (click its icon in the Activity Bar on the left or press ctrl-shift-d or command-shift-d)

  3. Click the Run and Debug button and choose Java if prompted (or click "create a launch.json file", choose GraphqlServerApplication from the RUN AND DEBUG dropdown, and click the Start Debugging button or press F5)

  4. Open http://localhost:8080/graphiql in a browser and submit a query via GraphiQL e.g. query { bookById(id: "book-1") { id } }

  5. The error & stack trace appears in the log:

2024-04-20T12:28:45.238-05:00 ERROR 72904 --- [nio-8080-exec-1] s.g.e.ExceptionResolversExceptionHandler : Unresolved IllegalArgumentException for executionId 1ad15c0d-5378-d6c5-569a-9c862f03614f

java.lang.IllegalArgumentException: Name for argument of type [java.lang.String] not specified, and parameter name information not found in class file either.
        at org.springframework.graphql.data.method.annotation.support.ArgumentMethodArgumentResolver.getArgumentName(ArgumentMethodArgumentResolver.java:92) ~[spring-graphql-1.2.4.jar:1.2.4]
        at org.springframework.graphql.data.method.annotation.support.ArgumentMethodArgumentResolver.resolveArgument(ArgumentMethodArgumentResolver.java:69) ~[spring-graphql-1.2.4.jar:1.2.4]
        at org.springframework.graphql.data.method.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:81) ~[spring-graphql-1.2.4.jar:1.2.4]
        at org.springframework.graphql.data.method.annotation.support.DataFetcherHandlerMethod.getMethodArgumentValues(DataFetcherHandlerMethod.java:177) ~[spring-graphql-1.2.4.jar:1.2.4]
        at org.springframework.graphql.data.method.annotation.support.DataFetcherHandlerMethod.invoke(DataFetcherHandlerMethod.java:119) ~[spring-graphql-1.2.4.jar:1.2.4]
        at org.springframework.graphql.data.method.annotation.support.DataFetcherHandlerMethod.invoke(DataFetcherHandlerMethod.java:107) ~[spring-graphql-1.2.4.jar:1.2.4]
        at org.springframework.graphql.data.method.annotation.support.AnnotatedControllerConfigurer$SchemaMappingDataFetcher.get(AnnotatedControllerConfigurer.java:680) ~[spring-graphql-1.2.4.jar:1.2.4]
        at org.springframework.graphql.execution.ContextDataFetcherDecorator.lambda$get$0(ContextDataFetcherDecorator.java:87) ~[spring-graphql-1.2.4.jar:1.2.4]
        at io.micrometer.context.ContextSnapshot.lambda$wrap$1(ContextSnapshot.java:106) ~[context-propagation-1.0.6.jar:1.0.6]
        at org.springframework.graphql.execution.ContextDataFetcherDecorator.get(ContextDataFetcherDecorator.java:87) ~[spring-graphql-1.2.4.jar:1.2.4]
        at graphql.execution.ExecutionStrategy.invokeDataFetcher(ExecutionStrategy.java:311) ~[graphql-java-21.3.jar:na]
        at graphql.execution.ExecutionStrategy.fetchField(ExecutionStrategy.java:287) ~[graphql-java-21.3.jar:na]
        at graphql.execution.ExecutionStrategy.resolveFieldWithInfo(ExecutionStrategy.java:213) ~[graphql-java-21.3.jar:na]
        at graphql.execution.AsyncExecutionStrategy.execute(AsyncExecutionStrategy.java:55) ~[graphql-java-21.3.jar:na]
        at graphql.execution.Execution.executeOperation(Execution.java:161) ~[graphql-java-21.3.jar:na]
        at graphql.execution.Execution.execute(Execution.java:103) ~[graphql-java-21.3.jar:na]
        at graphql.GraphQL.execute(GraphQL.java:568) ~[graphql-java-21.3.jar:na]
        at graphql.GraphQL.lambda$parseValidateAndExecute$13(GraphQL.java:487) ~[graphql-java-21.3.jar:na]
        at java.base/java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:1187) ~[na:na]
        at java.base/java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2309) ~[na:na]
        at graphql.GraphQL.parseValidateAndExecute(GraphQL.java:482) ~[graphql-java-21.3.jar:na]
        at graphql.GraphQL.lambda$executeAsync$9(GraphQL.java:440) ~[graphql-java-21.3.jar:na]
        at java.base/java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:1187) ~[na:na]
        at java.base/java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2309) ~[na:na]
        at graphql.GraphQL.executeAsync(GraphQL.java:428) ~[graphql-java-21.3.jar:na]
        at org.springframework.graphql.execution.DefaultExecutionGraphQlService.lambda$execute$2(DefaultExecutionGraphQlService.java:84) ~[spring-graphql-1.2.4.jar:1.2.4]
        at reactor.core.publisher.MonoDeferContextual.subscribe(MonoDeferContextual.java:47) ~[reactor-core-3.6.0.jar:3.6.0]
        at reactor.core.publisher.Mono.subscribe(Mono.java:4512) ~[reactor-core-3.6.0.jar:3.6.0]
        at reactor.core.publisher.Mono.subscribeWith(Mono.java:4578) ~[reactor-core-3.6.0.jar:3.6.0]
        at reactor.core.publisher.Mono.toFuture(Mono.java:5090) ~[reactor-core-3.6.0.jar:3.6.0]
        at org.springframework.core.ReactiveAdapterRegistry$ReactorRegistrar.lambda$registerAdapters$5(ReactiveAdapterRegistry.java:290) ~[spring-core-6.1.1.jar:6.1.1]
        at org.springframework.core.ReactiveAdapter.fromPublisher(ReactiveAdapter.java:121) ~[spring-core-6.1.1.jar:6.1.1]
        at org.springframework.web.servlet.function.DefaultAsyncServerResponse.create(DefaultAsyncServerResponse.java:186) ~[spring-webmvc-6.1.1.jar:6.1.1]
        at org.springframework.web.servlet.function.ServerResponse.async(ServerResponse.java:249) ~[spring-webmvc-6.1.1.jar:6.1.1]
        at org.springframework.graphql.server.webmvc.GraphQlHttpHandler.handleRequest(GraphQlHttpHandler.java:111) ~[spring-graphql-1.2.4.jar:1.2.4]
        at org.springframework.web.servlet.function.support.HandlerFunctionAdapter.handle(HandlerFunctionAdapter.java:107) ~[spring-webmvc-6.1.1.jar:6.1.1]
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) ~[spring-webmvc-6.1.1.jar:6.1.1]
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) ~[spring-webmvc-6.1.1.jar:6.1.1]
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-6.1.1.jar:6.1.1]
        at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) ~[spring-webmvc-6.1.1.jar:6.1.1]
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) ~[tomcat-embed-core-10.1.16.jar:6.0]
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.1.1.jar:6.1.1]
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.16.jar:6.0]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-10.1.16.jar:10.1.16]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.1.1.jar:6.1.1]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.1.jar:6.1.1]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.1.1.jar:6.1.1]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.1.jar:6.1.1]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.1.1.jar:6.1.1]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.1.jar:6.1.1]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:340) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1744) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
        at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

I believe the problem is related to the Parameter Name Retention change in Spring Framework 6.2:

https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-6.x#parameter-name-retention

The upgrade guidance contains potential solutions for IntelliJ IDEA and Eclipse IDE but no mention of Visual Studio Code. I've looked for some way to configure VS Code to add the recommended -parameters parameter to the Java compiler when Run and Debug is used, but I have not been able to find a way to do this.

bclozel commented 5 months ago

It seems VSCode (at least with the Java extension I've tried) is using Eclipse JDT compiler. You're right, there's not so much documentation around it. I've found how to configure global preferences and the complete reference of the JDT Eclipse compiler options.

I have created a .settings/org.eclipse.jdt.core.prefs file in my project with the content:

org.eclipse.jdt.core.compiler.codegen.methodParameters=generate

It worked for me. Let us know how it goes and if you've found better documentation. I can improve the Spring Framework wiki if this works.

jjavery commented 5 months ago

@bclozel thanks for this, I added the file with the line you suggested and I'm no longer seeing IllegalArgumentException when using Run and Debug in VS Code.

Oddly, VS Code (or the JDT Eclipse Complier) is now creating a second file in the .settings folder: .settings/org.eclipse.buildship.core.prefs and based on what I'm seeing within this file (paths within the local filesystem) I would advise excluding this second file from source control (add to .gitignore).

With regard to the Spring Framework Wiki:

  1. I found that the recommended changes for Gradle tasks were not necessary. For example, this build.gradle does not include the recommended changes, but the project appears to work fine when running with ./gradlew bootRun.
  2. In case you find it useful: Would the problem I encountered here in spring-graphql come up in other Spring projects? My teammates use both IntelliJ and VS Code. I'd like to be able to point them to documentation describing how to configure their projects and IDEs so they can continue to run/debug our Spring-based projects without this problem. The Spring Framework 6.1 upgrade guidance isn't the first place we'd think to look for this, and especially won't be in the future when new Spring Framework versions are released. Is there a wiki page that describes how to work with Spring projects within an IDE? Perhaps the wiki improvements could go there.