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:
- Smaller Native Images: Only include what's actually used
- Better Performance: Avoid unnecessary reflection overhead
- Predictable Behavior: Eliminate runtime surprises
- Maintainable Configuration: Explicit declaration of dependencies
Best Practices:
- Start with the tracing agent to collect initial metadata
- Refine configurations to be as specific as possible
- Use programmatic configuration for dynamic cases
- Test thoroughly in both JVM and Native Image modes
- 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.