You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A GraphQL Subscription with an input field that is annotated with a validation directive gets called with an input parameter that conflicts with the validation directive.
The ExecutionResult contains the correct validation error specified for the directive.
Actual behavior
A GraphQL Subscription with an input field that is annotated with a validation directive gets called with an input parameter that conflicts with the validation directive.
The ExecutionResult contains the following error: java.lang.IllegalStateException: Expected Publisher for a subscription
Steps to reproduce
Have a GraphQL Subscription with an input field that is annotated with a validation directive.
E.g.:
I created a test case where I checked for the correct behavior of the validations. On a mutation it works as expected and when the input parameter conforms with the validation on a Subscription there is also no issue.
I hope anyone can help me figuring out the problem here.
When you clone my test repo and try it out yourself you'll see that the third test fails.
@TestvoidsubscribeToPostsWithInvalidInput() {
Stringquery = "subscription subscribeToPosts($title: String!){ subscribeToPosts(title: $title) }";
Map<String, Object> variables = Map.of("title", "test"); // conflict with @Size directiveExecutionResultexecutionResult = dgsQueryExecutor.execute(query, variables);
assertThat(executionResult.getErrors()).isNotEmpty();
assertThat(Optional.ofNullable(executionResult.getData())).isEmpty();
assertThat(executionResult.getErrors().get(0).getMessage())
.isEqualTo("/subscribeToPosts/title size must be between 5 and 50");
}
While debugging, setting a breakpoint for any IllegalStateExceptions, you can see that the expected error message can be found in a DataFetcherResult in the ReactiveAdapterRegistryHelper.
In the end however, the IllegalStateException is thrown by the ReactiveAdapter being null.
publicabstractclassReactiveAdapterRegistryHelper {
//.../** * Return a {@link Flux} for the given result Object, adapting to a * {@link Publisher} first if necessary via {@link ReactiveAdapterRegistry}. * @param result the result Object to adapt * @return a {@link Flux}, possibly empty if the result is {@code null} */publicstaticFlux<?> toSubscriptionFlux(@NullableObjectresult) {
if (result == null) {
returnFlux.empty();
}
if (resultinstanceofPublisher<?> publisher) {
returnFlux.from(publisher);
}
ReactiveAdapteradapter = registry.getAdapter(result.getClass());
Assert.state(adapter != null, "Expected Publisher for a subscription");
returnFlux.from(adapter.toPublisher(result));
}
//...
}
Here is the console output for that test:
2025-02-13T11:49:01.765+01:00 ERROR 30488 --- [ Test worker] g.d.e.DefaultDataFetcherExceptionHandler : Exception while executing data fetcher for /subscribeToPosts: Expected Publisher for a subscription
java.lang.IllegalStateException: Expected Publisher for a subscription
at org.springframework.util.Assert.state(Assert.java:79) ~[spring-core-6.2.2.jar:6.2.2]
at org.springframework.graphql.execution.ReactiveAdapterRegistryHelper.toSubscriptionFlux(ReactiveAdapterRegistryHelper.java:105) ~[spring-graphql-1.3.3.jar:1.3.3]
at org.springframework.graphql.execution.ContextDataFetcherDecorator.get(ContextDataFetcherDecorator.java:90) ~[spring-graphql-1.3.3.jar:1.3.3]
at graphql.execution.instrumentation.dataloader.FallbackDataLoaderDispatchStrategy.lambda$modifyDataFetcher$0(FallbackDataLoaderDispatchStrategy.java:25) ~[graphql-java-22.3.jar:na]
at graphql.execution.ExecutionStrategy.invokeDataFetcher(ExecutionStrategy.java:533) ~[graphql-java-22.3.jar:na]
at graphql.execution.ExecutionStrategy.fetchField(ExecutionStrategy.java:497) ~[graphql-java-22.3.jar:na]
at graphql.execution.ExecutionStrategy.fetchField(ExecutionStrategy.java:438) ~[graphql-java-22.3.jar:na]
at graphql.execution.SubscriptionExecutionStrategy.createSourceEventStream(SubscriptionExecutionStrategy.java:110) ~[graphql-java-22.3.jar:na]
at graphql.execution.SubscriptionExecutionStrategy.execute(SubscriptionExecutionStrategy.java:67) ~[graphql-java-22.3.jar:na]
at graphql.execution.Execution.executeOperation(Execution.java:181) ~[graphql-java-22.3.jar:na]
at graphql.execution.Execution.execute(Execution.java:117) ~[graphql-java-22.3.jar:na]
at graphql.GraphQL.execute(GraphQL.java:546) ~[graphql-java-22.3.jar:na]
at graphql.GraphQL.lambda$parseValidateAndExecute$13(GraphQL.java:476) ~[graphql-java-22.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:471) ~[graphql-java-22.3.jar:na]
at graphql.GraphQL.lambda$executeAsync$9(GraphQL.java:429) ~[graphql-java-22.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:418) ~[graphql-java-22.3.jar:na]
at org.springframework.graphql.execution.DefaultExecutionGraphQlService.lambda$execute$2(DefaultExecutionGraphQlService.java:104) ~[spring-graphql-1.3.3.jar:1.3.3]
at reactor.core.publisher.MonoDeferContextual.subscribe(MonoDeferContextual.java:47) ~[reactor-core-3.6.1.jar:3.6.1]
at reactor.core.publisher.Mono.subscribe(Mono.java:4512) ~[reactor-core-3.6.1.jar:3.6.1]
at reactor.core.publisher.Mono.block(Mono.java:1727) ~[reactor-core-3.6.1.jar:3.6.1]
at com.netflix.graphql.dgs.springgraphql.SpringGraphQLDgsQueryExecutor.execute(SpringGraphQLDgsQueryExecutor.kt:90) ~[graphql-dgs-spring-graphql-10.0.3.jar:10.0.3]
at com.netflix.graphql.dgs.DgsQueryExecutor.execute(DgsQueryExecutor.java:59) ~[graphql-dgs-10.0.3.jar:10.0.3]
at com.example.graphqlextendedvalidationissue.GraphqlExtendedValidationIssueApplicationTests.subscribeToPostsWithInvalidInput(GraphqlExtendedValidationIssueApplicationTests.java:79) ~[test/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:569) ~[na:na]
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:767) ~[junit-platform-commons-1.11.4.jar:1.11.4]
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) ~[junit-jupiter-engine-5.11.4.jar:5.11.4]
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131) ~[junit-jupiter-engine-5.11.4.jar:5.11.4]
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156) ~[junit-jupiter-engine-5.11.4.jar:5.11.4]
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147) ~[junit-jupiter-engine-5.11.4.jar:5.11.4]
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86) ~[junit-jupiter-engine-5.11.4.jar:5.11.4]
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103) ~[junit-jupiter-engine-5.11.4.jar:5.11.4]
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93) ~[junit-jupiter-engine-5.11.4.jar:5.11.4]
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106) ~[junit-jupiter-engine-5.11.4.jar:5.11.4]
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64) ~[junit-jupiter-engine-5.11.4.jar:5.11.4]
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45) ~[junit-jupiter-engine-5.11.4.jar:5.11.4]
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37) ~[junit-jupiter-engine-5.11.4.jar:5.11.4]
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92) ~[junit-jupiter-engine-5.11.4.jar:5.11.4]
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86) ~[junit-jupiter-engine-5.11.4.jar:5.11.4]
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$8(TestMethodTestDescriptor.java:217) ~[junit-jupiter-engine-5.11.4.jar:5.11.4]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213) ~[junit-jupiter-engine-5.11.4.jar:5.11.4]
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138) ~[junit-jupiter-engine-5.11.4.jar:5.11.4]
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68) ~[junit-jupiter-engine-5.11.4.jar:5.11.4]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:156) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) ~[na:na]
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:160) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) ~[na:na]
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:160) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) ~[junit-platform-engine-1.11.4.jar:1.11.4]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:198) ~[junit-platform-launcher-1.11.4.jar:1.11.4]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:169) ~[junit-platform-launcher-1.11.4.jar:1.11.4]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:93) ~[junit-platform-launcher-1.11.4.jar:1.11.4]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:58) ~[junit-platform-launcher-1.11.4.jar:1.11.4]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:141) ~[junit-platform-launcher-1.11.4.jar:1.11.4]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:57) ~[junit-platform-launcher-1.11.4.jar:1.11.4]
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:103) ~[junit-platform-launcher-1.11.4.jar:1.11.4]
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:85) ~[junit-platform-launcher-1.11.4.jar:1.11.4]
at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47) ~[junit-platform-launcher-1.11.4.jar:1.11.4]
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:124) ~[na:na]
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:99) ~[na:na]
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:94) ~[na:na]
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:63) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:569) ~[na:na]
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) ~[na:na]
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) ~[na:na]
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) ~[na:na]
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:92) ~[na:na]
at jdk.proxy1/jdk.proxy1.$Proxy4.stop(Unknown Source) ~[na:na]
at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:200) ~[na:na]
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:132) ~[na:na]
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) ~[na:na]
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) ~[na:na]
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) ~[na:na]
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:121) ~[na:na]
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71) ~[na:na]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) ~[gradle-worker.jar:na]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) ~[gradle-worker.jar:na]
Expected :"/subscribeToPosts/title size must be between 5 and 50"
Actual :"java.lang.IllegalStateException: Expected Publisher for a subscription"
<Click to see difference>
org.opentest4j.AssertionFailedError:
expected: "/subscribeToPosts/title size must be between 5 and 50"
but was: "java.lang.IllegalStateException: Expected Publisher for a subscription"
at com.example.graphqlextendedvalidationissue.GraphqlExtendedValidationIssueApplicationTests.subscribeToPostsWithInvalidInput(GraphqlExtendedValidationIssueApplicationTests.java:84)
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
The text was updated successfully, but these errors were encountered:
Prototype-42
changed the title
bug: graphql-dgs-extended-validation returns IllegalStateException in ExecutionResult of GraphQL Subscription
bug: graphql-dgs-extended-validation returns IllegalStateException instead of validation exception in ExecutionResult of GraphQL Subscription
Feb 13, 2025
Hi @Prototype-42 thanks for the reproducer. It doesn't looks like directives work within Subscriptions calls themselves. They do work on the mutations. The problem occurs because the graphql extended validations doesn't return Flux type on validation errors where as spring-graphql expects it in subscriptions, or expects a registered adapter for the type. It returns a DataFetcherResult. Here's where that happens.
What does work is applying the @Size annotation in the subscription java API itself, because graphql-java is correctly returning a Flux type for that validation case.
I'll follow up with some potential updates to graphql-java to see what the best recommended fix could be.
The only way spring-graphql gives us in terms of an extension point is to create a ReactiveAdapter for DataFetcherResult which is really a hack in this case.
I opened an issue in spring-graphql with this example ported over to spring-graphql annotations.
Expected behavior
Actual behavior
java.lang.IllegalStateException: Expected Publisher for a subscription
Steps to reproduce
Have a GraphQL Subscription with an input field that is annotated with a validation directive.
E.g.:
Then call the Subscription with an input parameter that conflicts with the validation directive.
E.g.:
Test case
GitHub Link
I created a test case where I checked for the correct behavior of the validations. On a mutation it works as expected and when the input parameter conforms with the validation on a Subscription there is also no issue.
I hope anyone can help me figuring out the problem here.
When you clone my test repo and try it out yourself you'll see that the third test fails.
While debugging, setting a breakpoint for any IllegalStateExceptions, you can see that the expected error message can be found in a
DataFetcherResult
in theReactiveAdapterRegistryHelper
.In the end however, the IllegalStateException is thrown by the
ReactiveAdapter
beingnull
.Here is the console output for that test:
The text was updated successfully, but these errors were encountered: