GraalVM Native support
Spring Framework 6.0 introduced the support infrastructure for compiling Spring applications to GraalVM Native images. If you are not familiar with GraalVM in general, how this differs from applications deployed on the JVM and what it means for Spring application, please refer to the dedicated Spring Boot 3.x GraalVM Native Image support documentation. Spring Boot also documents the know limitations with the GraalVM support in Spring.
GraphQL Java metadata
Since the static analysis of your application is done at build time, GraalVM might need extra hints if your application is looking up static resources, performing reflection or creating JDK proxies at runtime.
GraphQL Java is performing three tasks at runtime that Native Images are sensible to:
-
Loading resource bundles for message internationalization
-
Some reflection on internal types for schema inspection
-
Reflection on Java types that your application registers with the schema. This happens for example when GraphQL Java is fetching properties from application types
The first two items are handled via reachability metadata that has been contributed by the Spring team to the GraalVM reachability metadata repository. This metadata is automatically fetched by the native compilation tool when building an application that depends on GraphQL Java. This doesn’t cover our third item in the list, as those types are provided by the application itself and must be discovered by another mean.
Native Server applications support
In typical Spring for GraphQL applications, Java types tied to the GraphQL schema are exposed in @Controller
method signatures
as parameters or return types. During the Ahead Of Time processing phase of the build,
Spring or GraphQL will use its o.s.g.data.method.annotation.support.SchemaMappingBeanFactoryInitializationAotProcessor
to discover
the relevant types and register reachability metadata accordingly.
This is all done automatically for you if you are building a Spring Boot application with GraalVM support.
If your application is "manually" registering data fetchers, some types are not discoverable as a result.
You should then register them with Spring Framework’s @RegisterReflectionForBinding
:
import graphql.schema.DataFetcher;
import org.springframework.aot.hint.annotation.RegisterReflectionForBinding;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.data.query.QuerydslDataFetcher;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
@Configuration
@RegisterReflectionForBinding(Book.class) (3)
public class GraphQlConfiguration {
@Bean
RuntimeWiringConfigurer customWiringConfigurer(BookRepository bookRepository) { (1)
DataFetcher<Book> dataFetcher = QuerydslDataFetcher.builder(bookRepository).single();
return (wiringBuilder) -> wiringBuilder
.type("Query", (builder) -> builder.dataFetcher("book", dataFetcher)); (2)
}
}
1 | This application declares a RuntimeWiringConfigurer that "manually" adds a DataFetcher |
2 | Through this DataFetcher , the BookRepository will expose a Book type |
3 | @RegisterReflectionForBinding will register the relevant hints for the Book type and all types exposed as fields |
Client support
The GraphQlClient
is not necessarily present as a bean in the application context and it does not expose the Java types used in the schema in method signatures.
The AotProcessor
strategy described in the section above cannot be used as a result.
For client support, Spring for GraphQL embeds the relevant reachability metadata for the client infrastructure.
When it comes to Java types used by the application, applications should use a similar strategy as "manual" data fetchers using @RegisterReflectionForBinding
:
import reactor.core.publisher.Mono;
import org.springframework.aot.hint.annotation.RegisterReflectionForBinding;
import org.springframework.graphql.client.GraphQlClient;
import org.springframework.stereotype.Component;
@Component
@RegisterReflectionForBinding(Project.class) (2)
public class ProjectService {
private final GraphQlClient graphQlClient;
public ProjectService(GraphQlClient graphQlClient) {
this.graphQlClient = graphQlClient;
}
public Mono<Project> project(String projectSlug) {
String document = """
query projectWithReleases($projectSlug: ID!) {
project(slug: $projectSlug) {
name
releases {
version
}
}
}
""";
return this.graphQlClient.document(document)
.variable("projectSlug", projectSlug)
.retrieve("project")
.toEntity(Project.class); (1)
}
}
1 | In a Native image, we need to ensure that reflection can be performed on Project at runtime |
2 | @RegisterReflectionForBinding will register the relevant hints for the Project type and all types exposed as fields |