Developer-kit graalvm-native-image
Provides expert guidance for building GraalVM Native Image executables from Java applications. Use when converting JVM applications to native binaries, optimizing cold start times, reducing memory footprint, configuring native build tools for Maven or Gradle, resolving reflection and resource issues in native builds, or implementing framework-specific native support for Spring Boot, Quarkus, and Micronaut. Triggers include "graalvm native image", "native executable java", "java cold start optimization", "native build tools", "ahead of time compilation java", "reflection config graalvm", "native image build failure".
git clone https://github.com/giuseppe-trisciuoglio/developer-kit
T=$(mktemp -d) && git clone --depth=1 https://github.com/giuseppe-trisciuoglio/developer-kit "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/developer-kit-java/skills/graalvm-native-image" ~/.claude/skills/giuseppe-trisciuoglio-developer-kit-graalvm-native-image && rm -rf "$T"
plugins/developer-kit-java/skills/graalvm-native-image/SKILL.mdGraalVM Native Image for Java Applications
Expert skill for building high-performance native executables from Java applications using GraalVM Native Image, dramatically reducing startup time and memory consumption.
Overview
GraalVM Native Image compiles Java applications ahead-of-time (AOT) into standalone native executables. These executables start in milliseconds, require significantly less memory than JVM-based deployments, and are ideal for serverless functions, CLI tools, and microservices where fast startup and low resource usage are critical.
This skill provides a structured workflow to migrate JVM applications to native binaries, covering build tool configuration, framework-specific patterns, reflection metadata management, and an iterative approach to resolving native build failures.
When to Use
Use this skill when:
- Converting a JVM-based Java application to a GraalVM native executable
- Optimizing cold start times for serverless or containerized deployments
- Reducing memory footprint (RSS) of Java microservices
- Configuring Maven or Gradle with GraalVM Native Build Tools
- Resolving
,ClassNotFoundException
, or missing resource errors in native buildsNoSuchMethodException - Generating or editing
,reflect-config.json
, or other GraalVM metadata filesresource-config.json - Using the GraalVM tracing agent to collect reachability metadata
- Implementing
for Spring Boot native supportRuntimeHints - Building native images with Quarkus or Micronaut
Instructions
1. Contextual Project Analysis
Before any configuration, analyze the project to determine the build tool, framework, and dependencies:
Detect the build tool:
# Check for Maven if [ -f "pom.xml" ]; then echo "Build tool: Maven" # Check for Maven wrapper [ -f "mvnw" ] && echo "Maven wrapper available" fi # Check for Gradle if [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then echo "Build tool: Gradle" [ -f "build.gradle.kts" ] && echo "Kotlin DSL" [ -f "gradlew" ] && echo "Gradle wrapper available" fi
Detect the framework by analyzing dependencies:
- Spring Boot: Look for
inspring-boot-starter-*
orpom.xmlbuild.gradle - Quarkus: Look for
dependenciesquarkus-* - Micronaut: Look for
dependenciesmicronaut-* - Plain Java: No framework dependencies detected
Check the Java version:
java -version 2>&1 # GraalVM Native Image requires Java 17+ (recommended: Java 21+)
Identify potential native image challenges:
- Reflection-heavy libraries (Jackson, Hibernate, JAXB)
- Dynamic proxy usage (JDK proxies, CGLIB)
- Resource bundles and classpath resources
- JNI or native library dependencies
- Serialization requirements
2. Build Tool Configuration
Configure the appropriate build tool plugin based on the detected environment.
For Maven projects, add a dedicated
native profile to keep the standard build clean. See the Maven Native Profile Reference for full configuration.
Key Maven setup:
<profiles> <profile> <id>native</id> <build> <plugins> <plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId> <version>0.10.6</version> <extensions>true</extensions> <executions> <execution> <id>build-native</id> <goals> <goal>compile-no-fork</goal> </goals> <phase>package</phase> </execution> </executions> <configuration> <imageName>${project.artifactId}</imageName> <buildArgs> <buildArg>--no-fallback</buildArg> </buildArgs> </configuration> </plugin> </plugins> </build> </profile> </profiles>
Build with:
./mvnw -Pnative package
For Gradle projects, apply the
org.graalvm.buildtools.native plugin. See the Gradle Native Plugin Reference for full configuration.
Key Gradle setup (Kotlin DSL):
plugins { id("org.graalvm.buildtools.native") version "0.10.6" } graalvmNative { binaries { named("main") { imageName.set(project.name) buildArgs.add("--no-fallback") } } }
Build with:
./gradlew nativeCompile
3. Framework-Specific Configuration
Each framework has its own AOT strategy. Apply the correct configuration based on the detected framework.
Spring Boot (3.x+): Spring Boot has built-in GraalVM support with AOT processing. See the Spring Boot Native Reference for patterns including
RuntimeHints, @RegisterReflectionForBinding, and test support.
Key points:
- Use
3.x+ which includes the native profilespring-boot-starter-parent - Register reflection hints via
RuntimeHintsRegistrar - Run AOT processing with
goalprocess-aot - Build with:
or./mvnw -Pnative native:compile./gradlew nativeCompile
Quarkus and Micronaut: These frameworks are designed native-first and require minimal additional configuration. See the Quarkus & Micronaut Reference.
4. GraalVM Reachability Metadata
Native Image uses a closed-world assumption — all code paths must be known at build time. Dynamic features like reflection, resources, and proxies require explicit metadata configuration.
Metadata files are placed in
META-INF/native-image/<group.id>/<artifact.id>/:
| File | Purpose |
|---|---|
| Unified metadata (reflection, resources, JNI, proxies, bundles, serialization) |
| Legacy: Reflection registration |
| Legacy: Resource inclusion patterns |
| Legacy: Dynamic proxy interfaces |
| Legacy: Serialization registration |
| Legacy: JNI access registration |
See the Reflection & Resource Config Reference for complete format and examples.
5. The Iterative Fix Engine
Native image builds often fail due to missing metadata. Follow this iterative approach:
Step 1 — Execute the native build:
# Maven ./mvnw -Pnative package 2>&1 | tee native-build.log # Gradle ./gradlew nativeCompile 2>&1 | tee native-build.log
Step 2 — Parse build errors and identify the root cause:
Common error patterns and their fixes:
| Error Pattern | Cause | Fix |
|---|---|---|
| Missing reflection metadata | Add to or use |
| Method not registered for reflection | Add method to reflection config |
| Resource not included in native image | Add to |
| Dynamic proxy not registered | Add interface list to |
| Missing serialization metadata | Add to |
Step 3 — Apply fixes by updating the appropriate metadata file or using framework annotations.
Step 4 — Rebuild and verify. Repeat until the build succeeds.
Step 5 — If manual fixes are insufficient, use the GraalVM tracing agent to collect reachability metadata automatically. See the Tracing Agent Reference.
6. Validation and Benchmarking
Once the native build succeeds:
Verify the executable runs correctly:
# Run the native executable ./target/<app-name> # For Spring Boot, verify the application context loads curl http://localhost:8080/actuator/health
Measure startup time:
# Time the startup time ./target/<app-name> # For Spring Boot, check the startup log ./target/<app-name> 2>&1 | grep "Started .* in"
Measure memory footprint (RSS):
# On Linux ps -o rss,vsz,comm -p $(pgrep <app-name>) # On macOS ps -o rss,vsz,comm -p $(pgrep <app-name>)
Compare with JVM baseline:
| Metric | JVM | Native | Improvement |
|---|---|---|---|
| Startup time | ~2-5s | ~50-200ms | 10-100x |
| Memory (RSS) | ~200-500MB | ~30-80MB | 3-10x |
| Binary size | JRE + JARs | Single binary | Simplified |
7. Docker Integration
Build minimal container images with native executables:
# Multi-stage build FROM ghcr.io/graalvm/native-image-community:21 AS builder WORKDIR /app COPY . . RUN ./mvnw -Pnative package -DskipTests # Minimal runtime image FROM debian:bookworm-slim COPY --from=builder /app/target/<app-name> /app/<app-name> EXPOSE 8080 ENTRYPOINT ["/app/<app-name>"]
For Spring Boot applications, use
paketobuildpacks/builder-jammy-tiny with Cloud Native Buildpacks:
./mvnw -Pnative spring-boot:build-image
Best Practices
- Start with the tracing agent on complex projects to generate an initial metadata baseline
- Use the
profile to keep native-specific config separate from standard buildsnative - Prefer
to ensure a true native build (no JVM fallback)--no-fallback - Test with
to run JUnit tests in native modenativeTest - Use GraalVM Reachability Metadata Repository for third-party library metadata
- Minimize reflection — prefer constructor injection and compile-time DI where possible
- Include resource patterns explicitly rather than relying on classpath scanning
- Profile before and after — always measure startup and memory improvements
- Use Java 21+ for the best GraalVM compatibility and performance
- Keep GraalVM and Native Build Tools versions aligned
Examples
Example 1: Adding Native Support to a Spring Boot Maven Project
Scenario: You have a Spring Boot 3.x REST API and want to compile it to a native executable.
Step 1 — Add the native profile to
:pom.xml
<profiles> <profile> <id>native</id> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <id>process-aot</id> <goals> <goal>process-aot</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId> </plugin> </plugins> </build> </profile> </profiles>
Step 2 — Register reflection hints for DTOs:
@RestController @RegisterReflectionForBinding({UserDto.class, OrderDto.class}) public class UserController { @GetMapping("/users/{id}") public UserDto getUser(@PathVariable Long id) { return userService.findById(id); } }
Step 3 — Build and run:
./mvnw -Pnative native:compile ./target/myapp # Started MyApplication in 0.089 seconds
Example 2: Resolving a Reflection Error in Native Build
Scenario: Native build fails with
ClassNotFoundException for a Jackson-serialized DTO.
Error output:
com.oracle.svm.core.jdk.UnsupportedFeatureError: Reflection registration missing for class com.example.dto.PaymentResponse
Fix — Add to
:src/main/resources/META-INF/native-image/reachability-metadata.json
{ "reflection": [ { "type": "com.example.dto.PaymentResponse", "allDeclaredConstructors": true, "allDeclaredMethods": true, "allDeclaredFields": true } ] }
Or use the Spring Boot annotation approach:
@RegisterReflectionForBinding(PaymentResponse.class) @Service public class PaymentService { /* ... */ }
Example 3: Using the Tracing Agent for a Complex Project
Scenario: A project with many third-party libraries needs comprehensive reachability metadata.
# 1. Build the JAR ./mvnw package -DskipTests # 2. Run with the tracing agent java -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image \ -jar target/myapp.jar # 3. Exercise all endpoints curl http://localhost:8080/api/users curl -X POST http://localhost:8080/api/orders -H 'Content-Type: application/json' -d '{"item":"test"}' curl http://localhost:8080/actuator/health # 4. Stop the application (Ctrl+C), then build native ./mvnw -Pnative native:compile # 5. Verify ./target/myapp
Constraints and Warnings
Critical Constraints
- GraalVM Native Image requires Java 17+ (Java 21+ recommended for best compatibility)
- Closed-world assumption: All code paths must be known at build time — dynamic class loading, runtime bytecode generation, and
may not workMethodHandles.Lookup - Build time and memory: Native compilation is resource-intensive — expect 2-10 minutes and 4-8 GB RAM for typical projects
- Not all libraries are compatible: Libraries relying heavily on reflection, dynamic proxies, or CGLIB may require extensive metadata configuration
- AOT profiles are fixed at build time: Spring Boot
and@Profile
are evaluated during AOT processing, not at runtime@ConditionalOnProperty
Common Pitfalls
- Forgetting
: Without this flag, the build may silently produce a JVM fallback image instead of a true native executable--no-fallback - Incomplete tracing agent coverage: The agent only captures code paths exercised during the run — ensure all features are tested
- Version mismatches: Keep GraalVM JDK, Native Build Tools plugin, and framework versions aligned to avoid incompatibilities
- Classpath differences: The classpath at AOT/build time must match runtime — adding/removing JARs after native compilation causes failures
Security Considerations
- Native executables are harder to decompile than JARs, but are not tamper-proof
- Ensure secrets are not embedded in the native image at build time
- Use environment variables or external config for sensitive data
Troubleshooting
| Issue | Solution |
|---|---|
| Build runs out of memory | Increase build memory: in |
| Build takes too long | Use build cache, reduce classpath, enable quick build mode for dev |
| Application crashes at runtime | Missing reflection/resource metadata — run tracing agent |
| Spring Boot context fails to load | Check beans and profile-dependent config |
| Third-party library not compatible | Check GraalVM Reachability Metadata repo or add manual hints |