Maven Plugin GraalVM Usage
GraalVm is a high performance runtime from Oracle that supports Ahead-of-Time (AOT) compilation that allows you to build native images. By shifting compilation to the build time, we can create binaries that are already optimized so they start almost instantaneously with immediate peak performance. Compiled code is also much more memory efficient as we no longer need the big memory overhead of running the JVM.
In order to generate GraalVM Native image we need to provide the information about all the dynamic JVM features that our
application relies on. Since graphql-kotlin
generates schema directly from your source code using reflections, we need
to capture this information to make it available at build time. By default, graphql-kotlin
also relies on classpath scanning
to look up all polymorphic types implementations as well as to locate all the (Apollo) Federated entity types.
Ktor GraalVM Native Image
Given following schema
class NativeExampleQuery : Query {
fun helloWorld() = "Hello World"
}
We first need to configure our server to avoid class scanning. Even though our example schema does not contain any polymorphic types, we still need to explicitly opt-out of class scanning by providing type hierarchy.
fun Application.graphQLModule() {
install(GraphQL) {
schema {
packages = listOf("com.example")
queries = listOf(
HelloWorldQuery()
)
// mapping between interfaces/union KClass and their implementation KClasses
typeHierarchy = mapOf()
}
}
install(Routing) {
graphQLPostRoute()
graphiQLRoute()
}
}
We then need to update our build with native configuration
- Original POM File
- Native POM File
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>example-graalvm-server</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<kotlin.jvmTarget>17</kotlin.jvmTarget>
<kotlin.version>1.7.22</kotlin.version>
<graphql-kotlin.version>${latestGraphqlKotlinVersion}</graphql-kotlin.version>
<!-- lib versions -->
<ktor.version>2.2.4</ktor.version>
<logback.version>1.4.7</logback.version>
</properties>
<dependencies>
<dependency>
<groupId>com.expediagroup</groupId>
<artifactId>graphql-kotlin-ktor-server</artifactId>
<version>${graphql-kotlin.version}</version>
</dependency>
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-cio-jvm</artifactId>
<version>${ktor.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<configuration>
<jvmTarget>${kotlin.jvmTarget}</jvmTarget>
</configuration>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.example.ApplicationKt</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>example-graalvm-server</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<kotlin.jvmTarget>17</kotlin.jvmTarget>
<kotlin.version>1.7.22</kotlin.version>
<graphql-kotlin.version>${latestGraphqlKotlinVersion}</graphql-kotlin.version>
<!-- lib versions -->
<ktor.version>2.2.4</ktor.version>
<logback.version>1.4.7</logback.version>
<native-maven-plugin.version>0.9.21</native-maven-plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>com.expediagroup</groupId>
<artifactId>graphql-kotlin-ktor-server</artifactId>
<version>${graphql-kotlin.version}</version>
</dependency>
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-cio-jvm</artifactId>
<version>${ktor.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<configuration>
<jvmTarget>${kotlin.jvmTarget}</jvmTarget>
</configuration>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.example.ApplicationKt</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<!-- 1. configure GraphQL Kotlin GraalVM plugin -->
<plugin>
<groupId>com.expediagroup</groupId>
<artifactId>graphql-kotlin-maven-plugin</artifactId>
<version>${graphql-kotlin.version}</version>
<executions>
<execution>
<goals>
<goal>generate-graalvm-metadata</goal>
</goals>
<configuration>
<packages>com.example</packages>
<mainClassName>com.example.ApplicationKt</mainClassName>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- 2. configure GraalVM Native Maven plugin -->
<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${native-maven-plugin.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
<configuration>
<verbose>true</verbose>
<buildArgs>
<arg>--initialize-at-build-time=io.ktor,kotlin,ch.qos.logback,org.slf4j</arg>
<arg>-H:+ReportExceptionStackTraces</arg>
</buildArgs>
<metadataRepository>
<enabled>true</enabled>
</metadataRepository>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
We need to make following changes to be able to generate GraalVM native image:
- Configure GraphQL Kotlin plugin to generate GraalVM metadata
This goal has to run AFTER compile
but before package
phase. It defaults to process-classes
phase.
- Configure GraalVM Native Maven plugin
GraalVM recommends to create separate profile that simplifies native image creation. Alternatively you can also generate
native image by explicitly executing native-image
goal.
Once the build is configured we can then generate our native image by running package
command with native
profile.
> ./mvnw -Pnative package
Native executable image will then be generated under target
directory.
Spring GraalVM Native Image
Given following schema
@Component
class NativeExampleQuery : Query {
fun helloWorld() = "Hello World"
}
We first need to configure our server to avoid class scanning. Even though our example schema does not contain any polymorphic types, we still need to explicitly opt-out of class scanning by providing type hierarchy.
@SpringBootApplication
class Application {
@Bean
fun typeResolver(): GraphQLTypeResolver = SimpleTypeResolver(mapOf())
}
fun main(args: Array<String>) {
runApplication<Application>(*args)
}
We then need to update our build with native configuration
- Original POM File
- Native POM File
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>example-graalvm-server</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<kotlin.jvmTarget>17</kotlin.jvmTarget>
<kotlin.version>1.7.22</kotlin.version>
<graphql-kotlin.version>${latestGraphqlKotlinVersion}</graphql-kotlin.version>
<!-- plugin versions -->
<spring-boot.version>3.0.6</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>com.expediagroup</groupId>
<artifactId>graphql-kotlin-spring-server</artifactId>
<version>${graphql-kotlin.version}</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<configuration>
<args>
<arg>-Xjsr305=strict</arg>
</args>
<compilerPlugins>
<plugin>spring</plugin>
</compilerPlugins>
<jvmTarget>${kotlin.jvmTarget}</jvmTarget>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>example-graalvm-server</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<kotlin.jvmTarget>17</kotlin.jvmTarget>
<kotlin.version>1.7.22</kotlin.version>
<graphql-kotlin.version>${latestGraphqlKotlinVersion}</graphql-kotlin.version>
<!-- plugin versions -->
<spring-boot.version>3.0.6</spring-boot.version>
<native-maven-plugin.version>0.9.21</native-maven-plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>com.expediagroup</groupId>
<artifactId>graphql-kotlin-spring-server</artifactId>
<version>${graphql-kotlin.version}</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<configuration>
<args>
<arg>-Xjsr305=strict</arg>
</args>
<compilerPlugins>
<plugin>spring</plugin>
</compilerPlugins>
<jvmTarget>${kotlin.jvmTarget}</jvmTarget>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
<!-- 1. configure GraphQL Kotlin GraalVM plugin -->
<plugin>
<groupId>com.expediagroup</groupId>
<artifactId>graphql-kotlin-maven-plugin</artifactId>
<version>${graphql-kotlin.version}</version>
<executions>
<execution>
<goals>
<goal>generate-graalvm-metadata</goal>
</goals>
<configuration>
<packages>com.example</packages>
<mainClassName>com.example.ApplicationKt</mainClassName>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- 2. configure GraalVM Native Maven plugin -->
<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${native-maven-plugin.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
<configuration>
<verbose>true</verbose>
<metadataRepository>
<enabled>true</enabled>
</metadataRepository>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
We need to make following changes to be able to generate GraalVM native image:
- Configure GraphQL Kotlin plugin to generate GraalVM metadata
This goal has to run AFTER compile
but before package
phase. It defaults to process-classes
phase.
- Configure GraalVM Native Maven plugin
GraalVM recommends to create separate profile that simplifies native image creation. Alternatively you can also generate
native image by explicitly executing native-image
goal.
Once the build is configured we can then generate our native image by running package
command with native
profile.
> ./mvnw -Pnative package
Native executable image will then be generated under target
directory.