GraalVM Reachability Metadata in Java: A Comprehensive Guide

GraalVM Native Image uses static analysis to determine which classes, methods, and fields are reachable at runtime. Reachability metadata provides explicit guidance to the Native Image builder about elements that should be included even if they're not discovered through static analysis.

Understanding Reachability Metadata

Step 1: Core Concepts and Problem Statement

// Example demonstrating the need for reachability metadata
package com.example.reflection;
public class ReflectionExample {
// This class uses reflection which Native Image can't analyze statically
public void demonstrateReflectionIssue() {
try {
// Problem: Native Image can't statically determine which class is loaded
String className = System.getProperty("dynamic.class");
Class<?> clazz = Class.forName(className); // ✗ Unknown at build time
Object instance = clazz.newInstance();
// Method invocation via reflection
Method method = clazz.getMethod("execute");
method.invoke(instance); // ✗ Unknown method at build time
} catch (Exception e) {
e.printStackTrace();
}
}
// Resource access that needs metadata
public void loadResource() {
// Problem: Native Image doesn't know about this resource
InputStream stream = getClass().getResourceAsStream("/config.properties"); // ✗ Unknown resource
if (stream != null) {
// Process resource
}
}
// Dynamic proxy usage
public void createProxy() {
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
getClass().getClassLoader(),
new Class<?>[]{MyInterface.class}, // ✓ Known interface
new MyInvocationHandler()
);
proxy.doSomething(); // ✗ Implementation details unknown
}
}
interface MyInterface {
void doSomething();
}
class MyInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
// Dynamic invocation handling
return null;
}
}

JSON-Based Reachability Metadata

Step 2: Using reflect-config.json

{
"name": "com.example.reflection.ReflectionExample",
"allDeclaredConstructors": true,
"allPublicConstructors": true,
"allDeclaredMethods": true,
"allPublicMethods": true,
"allDeclaredFields": true,
"allPublicFields": true
}

More specific configuration:

[
{
"name": "com.example.ServiceImpl",
"methods": [
{
"name": "<init>",
"parameterTypes": []
},
{
"name": "execute",
"parameterTypes": []
}
]
},
{
"name": "com.example.DynamicClass",
"fields": [
{
"name": "configValue",
"type": "java.lang.String"
}
]
}
]

Step 3: Resource Configuration

resource-config.json:

{
"resources": {
"includes": [
{
"pattern": ".*\\.properties$"
},
{
"pattern": "META-INF/services/.*"
},
{
"pattern": "templates/.*\\.html$"
},
{
"pattern": "com/example/config/.*\\.json$"
}
],
"excludes": [
{
"pattern": ".*test\\.properties$"
}
]
},
"bundles": [
{
"name": "com.example.messages.Messages"
},
{
"name": "javax.servlet.LocalStrings"
}
]
}

Programmatic Metadata Configuration

Step 4: Using Native Image Configuration Builders

package com.example.metadata;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.hosted.RuntimeReflection;
import org.graalvm.nativeimage.hosted.RuntimeResourceAccess;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
public class CustomReachabilityFeature implements Feature {
@Override
public void beforeAnalysis(BeforeAnalysisAccess access) {
System.out.println("Configuring reachability metadata...");
// Register classes for reflection
registerReflection();
// Register resources
registerResources();
// Register dynamic proxies
registerProxies();
// Register JNI
registerJNIClasses();
// Register serialization
registerSerialization();
}
private void registerReflection() {
try {
// Register specific classes with all members
Class<?>[] reflectiveClasses = {
Class.forName("com.example.ServiceImpl"),
Class.forName("com.example.DynamicFactory"),
Class.forName("com.example.Orchestrator")
};
for (Class<?> clazz : reflectiveClasses) {
RuntimeReflection.register(clazz);
RuntimeReflection.register(clazz.getDeclaredConstructors());
RuntimeReflection.register(clazz.getDeclaredMethods());
RuntimeReflection.register(clazz.getDeclaredFields());
System.out.println("Registered reflection for: " + clazz.getName());
}
// Register specific methods
Class<?> serviceClass = Class.forName("com.example.ExternalService");
Method connectMethod = serviceClass.getDeclaredMethod("connect", String.class, int.class);
RuntimeReflection.register(connectMethod);
// Register specific constructors
Constructor<?> constructor = serviceClass.getDeclaredConstructor(String.class);
RuntimeReflection.register(constructor);
} catch (Exception e) {
throw new RuntimeException("Failed to register reflection", e);
}
}
private void registerResources() {
// Register resource patterns
RuntimeResourceAccess.addResource("com.example", "config/application.properties");
RuntimeResourceAccess.addResource("com.example", "templates/.*\\.html");
RuntimeResourceAccess.addResourceBundle("com.example.messages.Messages");
// Register all resources in a package
RuntimeResourceAccess.addResource("com.example", ".*\\.json");
System.out.println("Registered resource patterns");
}
private void registerProxies() {
// Register dynamic proxy interfaces
Class<?>[] proxyInterfaces = {
java.util.concurrent.Callable.class,
java.lang.Runnable.class,
com.example.CustomInterface.class
};
for (Class<?> iface : proxyInterfaces) {
RuntimeReflection.register(iface);
}
System.out.println("Registered proxy interfaces");
}
private void registerJNIClasses() {
// Register classes used by JNI
Class<?>[] jniClasses = {
Class.forName("com.example.NativeWrapper"),
Class.forName("com.example.JNIBridge")
};
for (Class<?> clazz : jniClasses) {
RuntimeReflection.register(clazz);
}
}
private void registerSerialization() {
// Register classes for Java serialization
Class<?>[] serializableClasses = {
Class.forName("com.example.SerializableConfig"),
Class.forName("com.example.UserSession")
};
for (Class<?> clazz : serializableClasses) {
RuntimeReflection.register(clazz);
// Register serialization-specific methods if needed
}
}
@Override
public String getDescription() {
return "Custom reachability metadata configuration for Native Image";
}
}

Annotation-Based Metadata Configuration

Step 5: Using GraalVM Native Image Annotations

package com.example.annotations;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
// Custom annotation to mark classes needing reflection
@java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE})
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
public @interface Reflectable {
boolean allMethods() default true;
boolean allFields() default false;
boolean allConstructors() default true;
}
// Runtime initialization annotation
@org.graalvm.nativeimage.ImageInfo.inDebugValue
public class DebugConfig {
// This field will be true when running in debug mode
public static final boolean DEBUG = org.graalvm.nativeimage.ImageInfo.inDebugBuild();
}
// Platform-specific code
@Platforms({Platform.LINUX.class, Platform.DARWIN.class})
public class UnixSpecificOperations {
public void performUnixOperation() {
// Unix-specific implementation
}
}
@Platforms(Platform.WINDOWS.class)
public class WindowsSpecificOperations {
public void performWindowsOperation() {
// Windows-specific implementation
}
}

Step 6: Annotated Service Classes

package com.example.services;
@Reflectable(allMethods = true, allConstructors = true)
public class AnnotatedService {
private String configuration;
private int port;
// This constructor will be available for reflection
public AnnotatedService() {
this.configuration = "default";
this.port = 8080;
}
public AnnotatedService(String configuration, int port) {
this.configuration = configuration;
this.port = port;
}
// This method will be available for reflection
public void initialize() {
System.out.println("Initializing service with config: " + configuration);
}
@org.graalvm.nativeimage.c.function.CEntryPoint
public static int nativeEntryPoint() {
// Native entry point for C interop
return 42;
}
// Dynamic method lookup
public void invokeDynamic(String methodName) {
try {
Method method = getClass().getMethod(methodName);
method.invoke(this);
} catch (Exception e) {
System.err.println("Failed to invoke method: " + methodName);
}
}
}
// Factory pattern with reflection
public class ServiceFactory {
@Reflectable
public Object createService(String serviceType) {
try {
Class<?> serviceClass = Class.forName("com.example.services." + serviceType);
return serviceClass.newInstance();
} catch (Exception e) {
throw new RuntimeException("Failed to create service: " + serviceType, e);
}
}
public static List<String> getAvailableServices() {
// This would typically scan classpath, but in Native Image we need metadata
return Arrays.asList("DatabaseService", "WebService", "QueueService");
}
}

Dynamic Proxy Configuration

Step 7: Proxy Configuration Files

proxy-config.json:

[
{
"interfaces": [
"com.example.services.DatabaseService",
"java.lang.AutoCloseable"
]
},
{
"interfaces": [
"com.example.services.WebService",
"java.util.concurrent.Callable"
]
},
{
"interfaces": [
"com.example.messaging.EventHandler"
]
}
]

Programmatic proxy registration:

public class ProxyRegistrationFeature implements Feature {
@Override
public void duringSetup(DuringSetupAccess access) {
// Register proxy configurations
registerProxyConfigurations();
}
private void registerProxyConfigurations() {
// Define proxy interfaces
Class<?>[] databaseProxyInterfaces = {
getClass("com.example.services.DatabaseService"),
AutoCloseable.class
};
Class<?>[] webServiceProxyInterfaces = {
getClass("com.example.services.WebService"),
Callable.class
};
// Register with Native Image
RuntimeReflection.register(databaseProxyInterfaces);
RuntimeReflection.register(webServiceProxyInterfaces);
System.out.println("Registered proxy configurations");
}
private Class<?> getClass(String className) {
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Class not found: " + className, e);
}
}
}

JNI Configuration

Step 8: JNI Reachability Metadata

jni-config.json:

{
"name": "com.example.jni.NativeOperations",
"methods": [
{
"name": "nativeMethod",
"parameterTypes": ["java.lang.String", "int"]
},
{
"name": "callbackMethod",
"parameterTypes": []
}
]
}

JNI wrapper with proper metadata:

package com.example.jni;
public class NativeOperations {
static {
System.loadLibrary("nativeops");
}
// Native methods that need JNI metadata
public native void nativeMethod(String input, int count);
public native String processData(byte[] data);
public native void registerCallback(Runnable callback);
// JNI callback methods
private void callbackMethod() {
System.out.println("Callback from native code");
}
// Method that uses JNI internally
public void performNativeOperation() {
try {
// This might use JNI internally
nativeMethod("test", 42);
// Process data through native code
byte[] data = new byte[100];
String result = processData(data);
System.out.println("Native processing result: " + result);
} catch (Exception e) {
System.err.println("Native operation failed: " + e.getMessage());
}
}
}
// JNI metadata feature
public class JNIMetadataFeature implements Feature {
@Override
public void beforeAnalysis(BeforeAnalysisAccess access) {
registerJNIClasses();
}
private void registerJNIClasses() {
Class<?>[] jniClasses = {
NativeOperations.class,
// Add other JNI-related classes
};
for (Class<?> clazz : jniClasses) {
// Register for reflection
RuntimeReflection.register(clazz);
RuntimeReflection.register(clazz.getDeclaredMethods());
RuntimeReflection.register(clazz.getDeclaredConstructors());
// Register fields that might be accessed by native code
RuntimeReflection.register(clazz.getDeclaredFields());
}
System.out.println("Registered JNI classes for Native Image");
}
}

Serialization Configuration

Step 9: Serialization Metadata

serialization-config.json:

{
"types": [
{
"name": "com.example.serialization.User",
"customTargetConstructorClass": "com.example.serialization.UserConstructor"
},
{
"name": "com.example.serialization.Configuration",
"customTargetConstructorClass": "com.example.serialization.ConfigConstructor"
}
],
"lambdaCapturingTypes": [
{
"name": "com.example.lambdas.Processor"
}
]
}

Serializable classes with metadata:

package com.example.serialization;
import java.io.Serializable;
import java.util.Objects;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private String email;
private transient String password; // Not serialized
public User() {
// Default constructor for deserialization
}
public User(String username, String email) {
this.username = username;
this.email = email;
}
// Getters and setters
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(username, user.username) && 
Objects.equals(email, user.email);
}
@Override
public int hashCode() {
return Objects.hash(username, email);
}
}
// Custom constructor for serialization
public class UserConstructor {
public UserConstructor() {
// Required for Native Image serialization
}
public User newInstance() {
return new User();
}
}

Advanced Metadata Patterns

Step 10: Complex Metadata Scenarios

package com.example.advanced;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.hosted.RuntimeClassInitialization;
public class AdvancedMetadataFeature implements Feature {
@Override
public void duringSetup(DuringSetupAccess access) {
// Configure class initialization
configureClassInitialization();
// Register complex reflection patterns
registerComplexReflection();
// Configure resource access
configureResourceAccess();
}
private void configureClassInitialization() {
// Classes that can be initialized at build time
RuntimeClassInitialization.initializeAtBuildTime(
"com.example.config.BuildTimeConfig",
"com.example.cache.StaticCache",
"com.example.security.CryptoUtils"
);
// Classes that must be initialized at runtime
RuntimeClassInitialization.initializeAtRunTime(
"com.example.runtime.DynamicConfig",
"com.example.web.SessionManager"
);
// Package-level initialization control
RuntimeClassInitialization.initializeAtBuildTime("com.example.staticpkg");
RuntimeClassInitialization.initializeAtRunTime("com.example.dynamicpkg");
}
private void registerComplexReflection() {
// Register classes with conditional reflection
registerConditionalReflection();
// Register method handles
registerMethodHandles();
// Register resource bundles
registerResourceBundles();
}
private void registerConditionalReflection() {
// Only register reflection if certain conditions are met
String environment = System.getProperty("app.environment", "production");
if ("development".equals(environment)) {
// Register debug classes
Class<?>[] debugClasses = {
getClass("com.example.debug.DebugUtils"),
getClass("com.example.debug.Profiler")
};
for (Class<?> clazz : debugClasses) {
RuntimeReflection.register(clazz);
}
}
}
private void registerMethodHandles() {
try {
// Register method handles for dynamic invocation
Class<?> serviceClass = getClass("com.example.services.DynamicService");
Method handleMethod = serviceClass.getDeclaredMethod("getMethodHandle", String.class);
RuntimeReflection.register(handleMethod);
} catch (Exception e) {
System.err.println("Failed to register method handles: " + e.getMessage());
}
}
private void registerResourceBundles() {
// Register resource bundles
String[] bundles = {
"com.example.messages.AppMessages",
"com.example.errors.ErrorMessages",
"com.example.validation.ValidationMessages"
};
for (String bundle : bundles) {
RuntimeResourceAccess.addResourceBundle(bundle);
}
}
private void configureResourceAccess() {
// Include resources
RuntimeResourceAccess.addResource("com.example", "templates/.*");
RuntimeResourceAccess.addResource("com.example", "i18n/.*");
RuntimeResourceAccess.addResource("META-INF", "services/.*");
// Exclude development resources
RuntimeResourceAccess.addResourceExclusion(".*test.*");
RuntimeResourceAccess.addResourceExclusion(".*\\.gitkeep");
}
private Class<?> getClass(String className) {
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Class not found: " + className, e);
}
}
@Override
public void afterRegistration(AfterRegistrationAccess access) {
System.out.println("Advanced metadata configuration completed");
}
}

Testing Reachability Metadata

Step 11: Testing Configuration

package com.example.test;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
import java.lang.reflect.Method;
import java.util.ServiceLoader;
import static org.junit.jupiter.api.Assertions.*;
public class ReachabilityMetadataTest {
@BeforeAll
static void setup() {
// Verify Native Image context
if (isNativeImage()) {
System.out.println("Running in Native Image context");
}
}
@Test
void testReflectionAccess() {
try {
// Test that reflective class loading works
Class<?> serviceClass = Class.forName("com.example.services.AnnotatedService");
Object instance = serviceClass.newInstance();
// Test method invocation via reflection
Method initializeMethod = serviceClass.getMethod("initialize");
initializeMethod.invoke(instance);
assertNotNull(instance, "Reflective instance creation should work");
} catch (Exception e) {
fail("Reflection test failed: " + e.getMessage());
}
}
@Test
void testResourceAccess() {
// Test that resources are accessible
try (var stream = getClass().getResourceAsStream("/config/application.properties")) {
assertNotNull(stream, "Application properties should be accessible");
} catch (Exception e) {
fail("Resource access test failed: " + e.getMessage());
}
}
@Test
void testServiceLoader() {
// Test ServiceLoader functionality
ServiceLoader<com.example.spi.ServiceProvider> loader = 
ServiceLoader.load(com.example.spi.ServiceProvider.class);
int serviceCount = 0;
for (com.example.spi.ServiceProvider provider : loader) {
serviceCount++;
assertNotNull(provider, "Service provider should not be null");
}
assertTrue(serviceCount > 0, "At least one service provider should be found");
}
@Test
void testDynamicProxy() {
// Test dynamic proxy creation
ClassLoader classLoader = getClass().getClassLoader();
Class<?>[] interfaces = { Runnable.class, AutoCloseable.class };
Object proxy = java.lang.reflect.Proxy.newProxyInstance(
classLoader,
interfaces,
(proxy1, method, args) -> null
);
assertNotNull(proxy, "Dynamic proxy should be created successfully");
assertTrue(Runnable.class.isInstance(proxy), "Proxy should implement Runnable");
assertTrue(AutoCloseable.class.isInstance(proxy), "Proxy should implement AutoCloseable");
}
@Test
void testSerialization() {
try {
// Test serialization round-trip
com.example.serialization.User user = new com.example.serialization.User("test", "[email protected]");
// Serialize
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream(baos);
oos.writeObject(user);
oos.close();
// Deserialize
java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream(baos.toByteArray());
java.io.ObjectInputStream ois = new java.io.ObjectInputStream(bais);
com.example.serialization.User deserialized = (com.example.serialization.User) ois.readObject();
assertEquals(user, deserialized, "Serialization round-trip should work");
} catch (Exception e) {
fail("Serialization test failed: " + e.getMessage());
}
}
private static boolean isNativeImage() {
return System.getProperty("org.graalvm.nativeimage.imagecode") != null;
}
}

Build Configuration and Maven Integration

Step 12: Maven Configuration for Native Image

<?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-image-app</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<graalvm.version>22.3.0</graalvm.version>
<native.maven.plugin.version>0.9.19</native.maven.plugin.version>
</properties>
<dependencies>
<!-- GraalVM SDK for Native Image -->
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId>
<version>${graalvm.version}</version>
</dependency>
<!-- Native Image annotations -->
<dependency>
<groupId>org.graalvm.nativeimage</groupId>
<artifactId>svm</artifactId>
<version>${graalvm.version}</version>
<scope>provided</scope>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Native Image Maven Plugin -->
<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>build</goal>
</goals>
<phase>package</phase>
</execution>
<execution>
<id>test-native</id>
<goals>
<goal>test</goal>
</goals>
<phase>test</phase>
</execution>
</executions>
<configuration>
<imageName>${project.artifactId}</imageName>
<mainClass>com.example.Main</mainClass>
<buildArgs>
<buildArg>--verbose</buildArg>
<buildArg>--no-fallback</buildArg>
<buildArg>-H:+ReportExceptionStackTraces</buildArg>
<buildArg>-H:EnableURLProtocols=http,https</buildArg>
<buildArg>-H:+AddAllCharsets</buildArg>
<buildArg>-H:ReflectionConfigurationFiles=reflect-config.json</buildArg>
<buildArg>-H:ResourceConfigurationFiles=resource-config.json</buildArg>
<buildArg>-H:JNIConfigurationFiles=jni-config.json</buildArg>
<buildArg>-H:ProxyConfigurationFiles=proxy-config.json</buildArg>
<buildArg>-H:SerializationConfigurationFiles=serialization-config.json</buildArg>
</buildArgs>
<jarFile>${project.build.directory}/${project.artifactId}-${project.version}.jar</jarFile>
</configuration>
</plugin>
<!-- Build helper for metadata files -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.4.0</version>
<executions>
<execution>
<id>add-resource</id>
<phase>generate-resources</phase>
<goals>
<goal>add-resource</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>src/main/resources/META-INF/native-image</directory>
<targetPath>META-INF/native-image</targetPath>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

Best Practices and Troubleshooting

Step 13: Metadata Management Guidelines

package com.example.bestpractices;
import java.util.*;
public class ReachabilityBestPractices {
// 1. Use specific configuration over broad patterns
public static class SpecificConfiguration {
// Good: Specific method registration
public void registerSpecificMethods() {
// Instead of registering all methods, register only needed ones
try {
Class<?> serviceClass = Class.forName("com.example.Service");
Method specificMethod = serviceClass.getDeclaredMethod("process", String.class);
// Register only this specific method
} catch (Exception e) {
// Handle exception
}
}
// Avoid: Broad reflection registration
public void avoidBroadRegistration() {
// This includes many unnecessary methods
// RuntimeReflection.register(allDeclaredMethods); // ✗ Avoid
}
}
// 2. Use build-time initialization where possible
public static class BuildTimeInitialization {
private static final Map<String, String> CONFIG = loadConfigAtBuildTime();
private static Map<String, String> loadConfigAtBuildTime() {
// Configuration loaded at build time
Map<String, String> config = new HashMap<>();
config.put("timeout", "5000");
config.put("retries", "3");
return Collections.unmodifiableMap(config);
}
}
// 3. Handle conditional reflection properly
public static class ConditionalReflection {
public void registerConditional() {
String profile = System.getProperty("spring.profiles.active", "default");
if ("development".equals(profile)) {
registerDevelopmentClasses();
} else {
registerProductionClasses();
}
}
private void registerDevelopmentClasses() {
// Register debug and development classes
}
private void registerProductionClasses() {
// Register only production-essential classes
}
}
// 4. Monitor and optimize metadata
public static class MetadataOptimization {
public void analyzeMetadata() {
// Use Native Image agent to collect metadata
// Then refine the collected configuration
}
public void removeUnusedMetadata() {
// Regularly review and remove unused:
// - Reflection entries
// - Resource patterns  
// - Proxy configurations
}
}
// 5. Testing strategies
public static class MetadataTesting {
public void testAllReflectiveAccess() {
// Test every reflective access point
testClassForName();
testMethodInvocation();
testFieldAccess();
testResourceLoading();
testServiceLoader();
}
private void testClassForName() {
String[] classesToTest = {
"com.example.ServiceImpl",
"com.example.Factory",
"com.example.Utility"
};
for (String className : classesToTest) {
try {
Class.forName(className);
System.out.println("✓ Class accessible: " + className);
} catch (ClassNotFoundException e) {
System.err.println("✗ Class not found: " + className);
}
}
}
// Similar methods for other reflective operations...
}
}
// Troubleshooting common issues
public class ReachabilityTroubleshooting {
public static void diagnoseIssues() {
// Common issues and solutions:
// 1. ClassNotFoundException at runtime
//    Solution: Add class to reflect-config.json
// 2. Method not found during reflection
//    Solution: Register specific method in metadata
// 3. Resources not found
//    Solution: Add resource pattern to resource-config.json
// 4. Serialization failures  
//    Solution: Add class to serialization-config.json
// 5. Dynamic proxy creation failures
//    Solution: Register interfaces in proxy-config.json
}
public static void useDiagnosticFlags() {
// Enable diagnostic flags for troubleshooting
System.setProperty("graalvm.locatorEnabled", "true");
System.setProperty("nativeimage.detachall", "false");
// Common diagnostic flags:
// -H:+ReportExceptionStackTraces
// -H:+PrintClassInitialization
// -H:+TraceClassInitialization
// -H:+ReportUnsupportedElementsAtRuntime
}
}

Key Benefits and Summary

Benefits of Proper Reachability Metadata:

  1. Smaller Native Images: Only include what's actually used
  2. Better Performance: Avoid unnecessary reflection overhead
  3. Predictable Behavior: Eliminate runtime surprises
  4. Maintainable Configuration: Explicit declaration of dependencies

Best Practices:

  1. Start with the tracing agent to collect initial metadata
  2. Refine configurations to be as specific as possible
  3. Use programmatic configuration for dynamic cases
  4. Test thoroughly in both JVM and Native Image modes
  5. Regularly review and optimize metadata files

GraalVM reachability metadata is essential for building efficient and reliable native images. By properly configuring reflection, resources, JNI, serialization, and dynamic proxies, you can ensure your application works correctly while maintaining the performance benefits of Native Image.

Leave a Reply

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


Macro Nepal Helper