google-site-verification: google61fe8ba583a51912.html
GraalVM and Native Images in Java: Complete Guide to Native Java Applications

GraalVM is a high-performance JDK distribution that accelerates Java applications while enabling native compilation. Native Image technology compiles Java applications ahead-of-time into standalone native executables.


1. GraalVM Overview

What is GraalVM?

  • Universal VM: Supports multiple languages (Java, JavaScript, Python, R, Ruby)
  • High Performance: Advanced JIT compiler with optimizations
  • Native Image: AOT (Ahead-of-Time) compilation to native executables
  • Polyglot Capabilities: Run multiple languages in same runtime

GraalVM Editions:

  • Community Edition (CE): Free, open-source
  • Enterprise Edition (EE): Commercial with additional optimizations

Key Benefits:

  • Fast Startup: Milliseconds instead of seconds
  • Low Memory Footage: Reduced RSS memory usage
  • Instant Peak Performance: No warmup needed
  • Container Friendly: Small Docker images
  • Better Security: Reduced attack surface

2. Installation and Setup

Installing GraalVM

Using SDKMAN (Recommended)

# List available Java versions sdk list java # Install GraalVM sdk install java 22.3.1.r17-grl # Set as default sdk use java 22.3.1.r17-grl # Verify installation java -version native-image --version

Manual Installation

# Download from https://www.graalvm.org/downloads/ wget https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.3.1/graalvm-ce-java17-linux-amd64-22.3.1.tar.gz # Extract and set PATH tar -xzf graalvm-ce-java17-linux-amd64-22.3.1.tar.gz export GRAALVM_HOME=/path/to/graalvm export PATH=$GRAALVM_HOME/bin:$PATH # Install native-image gu install native-image

IDE Configuration

IntelliJ IDEA

<!-- Add to pom.xml for Maven --> <properties> <graalvm.home>/path/to/graalvm</graalvm.home> </properties>

VS Code

{ "java.home": "/path/to/graalvm", "java.configuration.runtimes": [ { "name": "JavaGraalVM", "path": "/path/to/graalvm", "default": true } ] }

3. Native Image Basics

Simple Native Image Example

// HelloWorld.java public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, Native World!"); System.out.println("Running on: " + System.getProperty("os.name")); System.out.println("Java version: " + System.getProperty("java.version")); } }

Compilation Commands

# Compile with regular javac javac HelloWorld.java # Create native image native-image HelloWorld # Run native executable ./helloworld # With specific name native-image -o hello-app HelloWorld # Build with detailed output native-image --verbose HelloWorld

Build Configuration

# Common native-image options native-image \ --no-fallback \ --enable-https \ --enable-http \ --enable-url-protocols=http,https \ -H:Name=myapp \ -H:Class=com.myapp.Main \ -H:Path=./build \ -H:Log=registerResource \ --report-unsupported-elements-at-runtime \ --initialize-at-build-time=org.slf4j \ -jar myapp.jar

4. Building Native Spring Boot Applications

Maven Configuration

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>native-spring-app</artifactId> <version>1.0.0</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.1.0</version> <relativePath/> </parent> <properties> <java.version>17</java.version> <graalvm.version>22.3.1</graalvm.version> <spring-native.version>0.12.1</spring-native.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.experimental</groupId> <artifactId>spring-native</artifactId> <version>${spring-native.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId> <version>0.9.19</version> <executions> <execution> <id>build-native</id> <goals> <goal>compile-no-fork</goal> </goals> <phase>package</phase> </execution> </executions> <configuration> <mainClass>com.example.NativeSpringApplication</mainClass> <buildArgs> <buildArg>--enable-https</buildArg> <buildArg>--enable-http</buildArg> <buildArg>-H:+ReportExceptionStackTraces</buildArg> </buildArgs> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <image> <builder>paketobuildpacks/builder:tiny</builder> <env> <BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE> </env> </image> </configuration> </plugin> </plugins> </build> </project>

Spring Boot 3 Native Application

// NativeSpringApplication.java package com.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.time.Instant; @SpringBootApplication public class NativeSpringApplication { public static void main(String[] args) { SpringApplication.run(NativeSpringApplication.class, args); } } @RestController class HelloController { private final UserService userService; public HelloController(UserService userService) { this.userService = userService; } @GetMapping("/") public String hello() { return "Hello from Native Spring Boot! Time: " + Instant.now(); } @GetMapping("/users") public List<User> getUsers() { return userService.getAllUsers(); } @GetMapping("/stats") public RuntimeStats getStats() { Runtime runtime = Runtime.getRuntime(); return new RuntimeStats( runtime.availableProcessors(), runtime.totalMemory(), runtime.freeMemory(), System.currentTimeMillis() ); } } // UserService.java package com.example; import org.springframework.stereotype.Service; import java.util.List; @Service public class UserService { public List<User> getAllUsers() { return List.of( new User(1L, "John Doe", "[email protected]"), new User(2L, "Jane Smith", "[email protected]"), new User(3L, "Bob Johnson", "[email protected]") ); } } // Records for data classes record User(Long id, String name, String email) {} record RuntimeStats(int processors, long totalMemory, long freeMemory, long timestamp) {}

Application Configuration

# application.properties spring.application.name=native-spring-app server.port=8080 # H2 Database for native testing spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= # JPA Configuration spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.show-sql=false # Native image specific spring.aop.proxy-target-class=false

5. Build Commands and Docker Integration

Maven Build Commands

# Regular JAR build mvn clean package # Build native image mvn -Pnative native:compile # Build with custom arguments mvn -Pnative native:compile -Dnative.buildArgs=--verbose # Build using Spring Boot mvn spring-boot:build-image # Run native executable ./target/native-spring-app

Docker Configuration

# Dockerfile for Native Image FROM ubuntu:22.04 as builder # Install necessary tools RUN apt-get update && apt-get install -y \ build-essential \ libz-dev \ zlib1g-dev \ && rm -rf /var/lib/apt/lists/* # Copy native executable COPY target/native-spring-app /app/native-spring-app # Create non-root user RUN useradd -m -u 1000 javauser USER javauser # Expose port EXPOSE 8080 # Run application ENTRYPOINT ["/app/native-spring-app"]

Multi-stage Docker Build

# Multi-stage Dockerfile FROM ghcr.io/graalvm/native-image:22.3.1 as builder WORKDIR /build # Copy source and build files COPY pom.xml . COPY src ./src # Build native image RUN mvn -Pnative native:compile # Runtime stage FROM gcr.io/distroless/base WORKDIR /app # Copy native executable COPY --from=builder /build/target/native-spring-app /app/ # Run as non-root USER 65534:65534 EXPOSE 8080 ENTRYPOINT ["/app/native-spring-app"]

Docker Compose

# docker-compose.yml version: '3.8' services: native-app: build: . ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=prod restart: unless-stopped # Add other services like database postgres: image: postgres:15 environment: POSTGRES_DB: myapp POSTGRES_USER: appuser POSTGRES_PASSWORD: secret ports: - "5432:5432"

6. Reflection and Configuration

Reflection Configuration

// reflect-config.json [ { "name": "com.example.User", "allDeclaredConstructors": true, "allPublicConstructors": true, "allDeclaredMethods": true, "allPublicMethods": true, "allDeclaredFields": true, "allPublicFields": true }, { "name": "com.example.RuntimeStats", "allDeclaredConstructors": true, "allPublicConstructors": true, "allDeclaredMethods": true, "allPublicMethods": true } ]

Native Image Properties File

# native-image.properties Args = --enable-https \ --enable-http \ -H:EnableURLProtocols=http,https \ -H:+ReportUnsupportedElementsAtRuntime \ -H:+ReportExceptionStackTraces \ --initialize-at-build-time=org.slf4j,ch.qos.logback \ --report-unsupported-elements-at-runtime

Programmatic Configuration

// ReflectionConfiguration.java package com.example.config; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.hosted.RuntimeReflection; import java.lang.reflect.Method; import java.util.Arrays; public class ReflectionConfiguration implements Feature { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { // Register classes for reflection registerForReflection(access, "com.example.User"); registerForReflection(access, "com.example.RuntimeStats"); registerForReflection(access, "com.example.HelloController"); // Register resource bundles registerResourceBundles("messages", "validation"); } private void registerForReflection(BeforeAnalysisAccess access, String className) { try { Class<?> clazz = Class.forName(className); RuntimeReflection.register(clazz); RuntimeReflection.register(clazz.getDeclaredConstructors()); RuntimeReflection.register(clazz.getDeclaredMethods()); RuntimeReflection.register(clazz.getDeclaredFields()); } catch (ClassNotFoundException e) { System.err.println("Class not found for reflection: " + className); } } private void registerResourceBundles(String... baseNames) { Arrays.stream(baseNames).forEach(baseName -> { try { RuntimeReflection.register( java.util.ResourceBundle.getBundle(baseName).getClass() ); } catch (Exception e) { System.err.println("Resource bundle not found: " + baseName); } }); } }

7. Advanced Native Image Features

Building with Gradle

// build.gradle.kts plugins { java id("org.springframework.boot") version "3.1.0" id("io.spring.dependency-management") version "1.1.0" id("org.graalvm.buildtools.native") version "0.9.19" } repositories { mavenCentral() } dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-data-jpa") runtimeOnly("com.h2database:h2") } graalvmNative { binaries { named("main") { buildArgs.addAll( "--enable-https", "--enable-http", "-H:EnableURLProtocols=http,https", "-H:+ReportExceptionStackTraces", "--initialize-at-build-time=org.slf4j" ) } } } tasks.withType<Test> { useJUnitPlatform() }

Custom Native Image Build

// Custom native image builder package com.example; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; public class NativeImageBuilder { public static void buildNativeImage(String mainClass, String outputName) throws IOException, InterruptedException { List<String> command = new ArrayList<>(); command.add("native-image"); command.add("--no-fallback"); command.add("--enable-https"); command.add("--enable-http"); command.add("-H:EnableURLProtocols=http,https"); command.add("-H:Name=" + outputName); command.add("-H:Class=" + mainClass); command.add("-H:Path=./target"); command.add("-H:+ReportExceptionStackTraces"); command.add("--report-unsupported-elements-at-runtime"); command.add("--initialize-at-build-time=org.slf4j,ch.qos.logback"); command.add("-cp"); command.add("target/classes:target/dependency/*"); command.add(mainClass); ProcessBuilder pb = new ProcessBuilder(command); pb.inheritIO(); System.out.println("Building native image..."); Process process = pb.start(); int exitCode = process.waitFor(); if (exitCode == 0) { System.out.println("Native image built successfully: " + outputName); } else { throw new RuntimeException("Native image build failed with code: " + exitCode); } } public static void main(String[] args) throws Exception { buildNativeImage("com.example.NativeSpringApplication", "my-native-app"); } }

8. Testing Native Applications

JUnit 5 Testing Configuration

// Base test class for native compatibility package com.example; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.ResponseEntity; import org.springframework.test.context.TestPropertySource; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NativeSpringApplication.class ) @TestPropertySource(properties = { "spring.datasource.url=jdbc:h2:mem:testdb", "spring.jpa.hibernate.ddl-auto=create-drop" }) class NativeApplicationTests { @Test void contextLoads() { // Basic context loading test } @Test void helloEndpointWorks(TestRestTemplate restTemplate) { ResponseEntity<String> response = restTemplate.getForEntity("/", String.class); assertEquals(200, response.getStatusCodeValue()); assertTrue(response.getBody().contains("Hello from Native Spring Boot")); } @Test void usersEndpointReturnsData(TestRestTemplate restTemplate) { ResponseEntity<String> response = restTemplate.getForEntity("/users", String.class); assertEquals(200, response.getStatusCodeValue()); assertNotNull(response.getBody()); } }

Native Image Testing

# Test the native executable ./target/native-spring-app & APP_PID=$! # Wait for app to start sleep 5 # Test endpoints curl http://localhost:8080/ curl http://localhost:8080/users curl http://localhost:8080/stats # Kill the app kill $APP_PID

9. Performance Comparison

Benchmarking Class

package com.example; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.time.Instant; import java.util.concurrent.TimeUnit; public class PerformanceBenchmark { public static void main(String[] args) throws Exception { System.out.println("=== Performance Benchmark ==="); long startTime = System.nanoTime(); long jvmStartTime = getJvmStartTime(); // Application startup and initial operation performInitialization(); long endTime = System.nanoTime(); long totalTime = TimeUnit.NANOSECONDS.toMillis(endTime - startTime); long jvmUptime = TimeUnit.NANOSECONDS.toMillis(endTime - jvmStartTime); System.out.println("Total time: " + totalTime + " ms"); System.out.println("JVM uptime: " + jvmUptime + " ms"); System.out.println("Memory used: " + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024 / 1024 + " MB"); // First request simulation long firstRequestStart = System.nanoTime(); simulateFirstRequest(); long firstRequestTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - firstRequestStart); System.out.println("First request time: " + firstRequestTime + " ms"); } private static long getJvmStartTime() { RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); return TimeUnit.MILLISECONDS.toNanos(runtimeMXBean.getStartTime()); } private static void performInitialization() { // Simulate application initialization try { Thread.sleep(100); // Simulate startup work } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } private static void simulateFirstRequest() { // Simulate first API request try { Thread.sleep(50); // Simulate request processing } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }

Performance Results Example

# JVM Version # Startup: 1.5 - 3 seconds # Memory: 100-200 MB # First Request: 100-500 ms # Native Image Version # Startup: 0.01 - 0.05 seconds # Memory: 20-50 MB # First Request: 1-10 ms

10. Common Issues and Solutions

Troubleshooting Common Problems

// Common native image issues and solutions public class NativeImageTroubleshooting { // Issue 1: Missing reflection configuration public void reflectionIssue() { // Solution: Add reflect-config.json or use @RegisterForReflection } // Issue 2: Resource not found public void resourceIssue() { // Solution: Register resources in resource-config.json } // Issue 3: Dynamic proxy generation public void proxyIssue() { // Solution: Add proxy-config.json or use -H:DynamicProxyConfigurationFiles } // Issue 4: Class initialization public void classInitializationIssue() { // Solution: Use --initialize-at-build-time or --initialize-at-run-time } } // Annotation for easy reflection registration import io.micronaut.core.annotation.TypeHint; @TypeHint({ User.class, RuntimeStats.class, HelloController.class }) class ReflectionConfigurationHint {}

Useful Native Image Options

# Debugging options --verbose -H:+ReportExceptionStackTraces -H:+TraceClassInitialization -H:+PrintClassInitialization # Memory options -Xmx4G # Build memory -Xms2G # Initial heap size # Performance options -O2 # Optimization level (0-2) --gc=G1 # Garbage collector # Feature options --enable-all-security-services --install-exit-handlers

11. Production Best Practices

Security Considerations

package com.example.security; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.SecurityFilterChain; @Configuration public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .authorizeHttpRequests(auth -> auth .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() ) .csrf(csrf -> csrf.disable()) // For API applications .build(); } }

Monitoring and Health Checks

package com.example.health; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.stereotype.Component; @Component public class NativeAppHealthIndicator implements HealthIndicator { @Override public Health health() { try { // Custom health checks Runtime runtime = Runtime.getRuntime(); long usedMemory = runtime.totalMemory() - runtime.freeMemory(); long maxMemory = runtime.maxMemory(); if (usedMemory > maxMemory * 0.9) { return Health.down() .withDetail("memory", "Critical memory usage") .withDetail("used", usedMemory) .withDetail("max", maxMemory) .build(); } return Health.up() .withDetail("memory_used", usedMemory) .withDetail("memory_max", maxMemory) .withDetail("processors", runtime.availableProcessors()) .build(); } catch (Exception e) { return Health.down(e).build(); } } }

Conclusion

When to Use GraalVM Native Image:

✅ Good Use Cases:

  • Microservices and serverless functions
  • CLI tools and utilities
  • Resource-constrained environments
  • Applications requiring fast startup
  • Containerized deployments

❌ Less Suitable For:

  • Applications with heavy reflection use
  • Complex frameworks with dynamic classloading
  • Applications requiring JVM tooling (JMX, JFR)
  • Long-running applications where JIT optimization matters more

Key Benefits Summary:

  • Startup Time: 10-100x faster
  • Memory Usage: 5-10x lower
  • Instant Performance: No warmup required
  • Small Deployment Size: Single binary
  • Better Security: Reduced attack surface

Migration Strategy:

  1. Start Simple: Begin with basic applications
  2. Incremental Migration: Convert services one by one
  3. Testing: Comprehensive testing of native builds
  4. Monitoring: Monitor performance in production

GraalVM Native Images represent the future of Java in cloud-native environments, offering unprecedented performance characteristics while maintaining Java's productivity benefits.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper