The GraalVM SDK Native Image API allows to customize the native image generation, i.e., the ahead-of-time compilation of Java code to standalone executables
—
Core runtime functionality for native image execution, including isolate management, memory allocation, threading support, platform abstraction, and execution context information.
Query the current execution context to determine whether code is running during native image build time or at runtime.
/**
* Utility class providing information about the current execution context
*/
class ImageInfo {
/** Returns true if executing during native image generation */
static boolean inImageBuildtimeCode();
/** Returns true if executing in a native image at runtime */
static boolean inImageRuntimeCode();
/** Returns true if executing in any image context (build or runtime) */
static boolean inImageCode();
/** Returns true if the current executable is a native executable */
static boolean isExecutable();
/** Returns true if the current executable is a shared library */
static boolean isSharedLibrary();
}Usage Examples:
// Conditional behavior based on execution context
if (ImageInfo.inImageBuildtimeCode()) {
// Expensive initialization only during build
precomputeConstants();
} else if (ImageInfo.inImageRuntimeCode()) {
// Runtime-specific optimizations
enableFastPaths();
}
// Check executable type
if (ImageInfo.isSharedLibrary()) {
// Library-specific initialization
registerCallbacks();
}Comprehensive platform support with conditional compilation capabilities.
/**
* Root interface for all platform types
*/
interface Platform {}
/**
* Annotation to restrict code to specific platforms
*/
@interface Platforms {
Class<? extends Platform>[] value();
}
// Architecture platforms
interface AMD64 extends Platform {}
interface AARCH64 extends Platform {}
interface RISCV64 extends Platform {}
// Operating system platforms
interface LINUX extends Platform {}
interface ANDROID extends LINUX {}
interface DARWIN extends Platform {}
interface IOS extends DARWIN {}
interface MACOS extends DARWIN {}
interface WINDOWS extends Platform {}
// Base combinations
interface LINUX_AMD64_BASE extends LINUX, AMD64 {}
interface LINUX_AARCH64_BASE extends LINUX, AARCH64 {}
interface DARWIN_AMD64 extends DARWIN, AMD64 {}
interface DARWIN_AARCH64 extends DARWIN, AARCH64 {}
// Leaf platforms (concrete implementations)
class LINUX_AMD64 implements LINUX, LINUX_AMD64_BASE {}
class LINUX_AARCH64 implements LINUX, LINUX_AARCH64_BASE {}
class LINUX_RISCV64 implements LINUX, RISCV64 {}
class ANDROID_AARCH64 implements ANDROID, LINUX_AARCH64_BASE {}
class IOS_AMD64 implements IOS, DARWIN_AMD64 {}
class IOS_AARCH64 implements IOS, DARWIN_AARCH64 {}
class MACOS_AMD64 implements MACOS, DARWIN_AMD64 {}
class MACOS_AARCH64 implements MACOS, DARWIN_AARCH64 {}
class WINDOWS_AMD64 implements WINDOWS, AMD64 {}
class WINDOWS_AARCH64 implements WINDOWS, AARCH64 {}
// Special marker platform
class HOSTED_ONLY implements Platform {} // Build-time onlyUsage Examples:
// Platform-specific code
@Platforms({Platform.LINUX.class, Platform.DARWIN.class})
public class UnixSpecificCode {
public void doUnixStuff() {
// Only compiled on Unix-like platforms (Linux, Android, macOS, iOS)
}
}
// Mobile-specific code
@Platforms({Platform.ANDROID_AARCH64.class, Platform.IOS_AARCH64.class})
public class MobileOptimizations {
public void optimizeForMobile() {
// Mobile-specific implementations
}
}
// Runtime platform checks
if (Platform.includedIn(Platform.LINUX.class)) {
// Linux-specific runtime behavior (includes Android)
}
if (Platform.includedIn(Platform.ANDROID.class)) {
// Android-specific behavior
}
if (Platform.includedIn(Platform.MACOS.class)) {
// macOS-specific behavior
}
if (Platform.includedIn(Platform.IOS.class)) {
// iOS-specific behavior
}
// Architecture-specific optimizations
@Platforms(Platform.AMD64.class)
public void optimizedForAMD64() {
// AMD64-specific implementation (Intel/AMD 64-bit)
}
@Platforms(Platform.AARCH64.class)
public void optimizedForARM64() {
// ARM64-specific implementation (Apple Silicon, ARM servers)
}
@Platforms(Platform.RISCV64.class)
public void optimizedForRISCV() {
// RISC-V 64-bit specific implementation
}
// Specific platform combinations
@Platforms(Platform.WINDOWS_AARCH64.class)
public void windowsARM64Specific() {
// Windows on ARM64 (Surface Pro X, etc.)
}
@Platforms({Platform.MACOS_AARCH64.class, Platform.IOS_AARCH64.class})
public void appleARM64Optimizations() {
// Optimizations for Apple Silicon (M1/M2/M3)
}
// Build-time only code
@Platforms(Platform.HOSTED_ONLY.class)
public class BuildTimeUtilities {
// Only available during native image generation
}Key-value store for compile-time constant lookup and configuration sharing.
/**
* Registry for singleton objects available during native image generation
*/
class ImageSingletons {
/** Store a singleton instance for the given key class */
static <T> void add(Class<T> key, T value);
/** Retrieve the singleton instance for the given key class */
static <T> T lookup(Class<T> key);
/** Check if a singleton is registered for the given key class */
static boolean contains(Class<?> key);
}Usage Examples:
// Register configuration during build
ImageSingletons.add(MyConfig.class, new MyConfig());
// Lookup configuration at runtime
MyConfig config = ImageSingletons.lookup(MyConfig.class);
// Conditional access
if (ImageSingletons.contains(OptionalFeature.class)) {
OptionalFeature feature = ImageSingletons.lookup(OptionalFeature.class);
feature.enable();
}Multi-isolate support with independent memory spaces and cross-isolate communication.
/**
* Pointer to an isolate's runtime data structure
*/
interface Isolate extends PointerBase {}
/**
* Pointer to thread-specific data within an isolate
*/
interface IsolateThread extends PointerBase {}
/**
* Support for isolate creation, access, and management
*/
class Isolates {
/** Create a new isolate with specified parameters */
static IsolateThread createIsolate(CreateIsolateParameters params) throws IsolateException;
/** Attach the current thread to an existing isolate */
static IsolateThread attachCurrentThread(Isolate isolate) throws IsolateException;
/** Get current thread's structure for the specified isolate */
static IsolateThread getCurrentThread(Isolate isolate) throws IsolateException;
/** Get the isolate of a thread */
static Isolate getIsolate(IsolateThread thread) throws IsolateException;
/** Detach a thread from its isolate */
static void detachThread(IsolateThread thread) throws IsolateException;
/** Tear down an isolate and all its threads */
static void tearDownIsolate(IsolateThread thread) throws IsolateException;
/** Exception thrown during isolate operations */
static class IsolateException extends RuntimeException {
IsolateException(String message);
}
}
/**
* Parameters for isolate creation with builder pattern
*/
class CreateIsolateParameters {
/** Get default isolate creation parameters */
static CreateIsolateParameters getDefault();
/** Get reserved address space size */
UnsignedWord getReservedAddressSpaceSize();
/** Get auxiliary image path */
String getAuxiliaryImagePath();
/** Get auxiliary image reserved space size */
UnsignedWord getAuxiliaryImageReservedSpaceSize();
/** Get additional isolate arguments */
List<String> getArguments();
/** Get memory protection domain */
ProtectionDomain getProtectionDomain();
/** Builder for CreateIsolateParameters */
static class Builder {
Builder();
/** Set reserved virtual address space size */
Builder reservedAddressSpaceSize(UnsignedWord size);
/** Set auxiliary image file path */
Builder auxiliaryImagePath(String filePath);
/** Set auxiliary image reserved space size */
Builder auxiliaryImageReservedSpaceSize(UnsignedWord size);
/** Append isolate argument (Native Image syntax) */
Builder appendArgument(String argument);
/** Set memory protection domain */
Builder setProtectionDomain(ProtectionDomain domain);
/** Build the final parameters */
CreateIsolateParameters build();
}
}
/**
* Memory protection domain for isolates
*/
interface ProtectionDomain {
/** Singleton: no protection domain */
ProtectionDomain NO_DOMAIN = /* implementation */;
/** Singleton: create new protection domain */
ProtectionDomain NEW_DOMAIN = /* implementation */;
}
/**
* Utilities for accessing the current isolate
*/
class CurrentIsolate {
/** Get the current thread's IsolateThread pointer */
static IsolateThread getCurrentThread();
/** Get the current isolate */
static Isolate getIsolate();
}Usage Examples:
// Create a new isolate with custom parameters
CreateIsolateParameters params = new CreateIsolateParameters.Builder()
.reservedAddressSpaceSize(WordFactory.unsigned(1024 * 1024 * 1024)) // 1GB
.auxiliaryImagePath("/path/to/auxiliary.so")
.appendArgument("-XX:+AutomaticReferenceHandling")
.appendArgument("-XX:MaxRAMPercentage=50")
.setProtectionDomain(ProtectionDomain.NEW_DOMAIN)
.build();
try {
IsolateThread newIsolateThread = Isolates.createIsolate(params);
System.out.println("Created isolate with thread: " + newIsolateThread);
} catch (Isolates.IsolateException e) {
System.err.println("Failed to create isolate: " + e.getMessage());
}
// Work with current isolate
IsolateThread currentThread = CurrentIsolate.getCurrentThread();
Isolate currentIsolate = CurrentIsolate.getIsolate();
// Cross-isolate operations with error handling
try {
Isolate targetIsolate = Isolates.getIsolate(someThread);
IsolateThread attachedThread = Isolates.attachCurrentThread(targetIsolate);
// Do work in the attached isolate
performWork();
// Clean up
Isolates.detachThread(attachedThread);
} catch (Isolates.IsolateException e) {
System.err.println("Isolate operation failed: " + e.getMessage());
}
// Complete isolate lifecycle
try {
// Create isolate with default parameters
IsolateThread isolateThread = Isolates.createIsolate(CreateIsolateParameters.getDefault());
// Get the isolate handle
Isolate isolate = Isolates.getIsolate(isolateThread);
// Attach another thread
IsolateThread secondThread = Isolates.attachCurrentThread(isolate);
// Detach the second thread
Isolates.detachThread(secondThread);
// Tear down the entire isolate
Isolates.tearDownIsolate(isolateThread);
} catch (Isolates.IsolateException e) {
System.err.println("Isolate lifecycle error: " + e.getMessage());
}Stack allocation, unmanaged memory operations, and pinned objects for native interop.
/**
* Stack frame memory allocation utilities
*/
class StackValue {
/** Allocate memory on the stack for the given type */
static <T extends PointerBase> T get(Class<T> structType);
/** Allocate an array on the stack */
static <T extends PointerBase> T get(int length, Class<T> elementType);
/** Allocate memory of specific size on the stack */
static <T extends PointerBase> T get(int sizeInBytes);
}
/**
* malloc/free-style unmanaged memory operations
*/
class UnmanagedMemory {
/** Allocate unmanaged memory */
static <T extends PointerBase> T malloc(int sizeInBytes);
/** Reallocate unmanaged memory */
static <T extends PointerBase> T realloc(T ptr, int newSizeInBytes);
/** Free unmanaged memory */
static void free(PointerBase ptr);
/** Copy memory between locations */
static void copy(PointerBase from, PointerBase to, int sizeInBytes);
}
/**
* Holder for pinned objects with AutoCloseable support
*/
class PinnedObject implements AutoCloseable {
/** Create a pinned object from an array */
static PinnedObject create(Object object);
/** Get pointer to the start of the pinned object */
<T extends PointerBase> T addressOfArrayElement(int index);
/** Release the pinned object */
void close();
}Usage Examples:
// Stack allocation
CIntPointer stackInt = StackValue.get(CIntPointer.class);
stackInt.write(42);
// Array on stack
CIntPointer stackArray = StackValue.get(10, CIntPointer.class);
for (int i = 0; i < 10; i++) {
stackArray.addressOf(i).write(i);
}
// Unmanaged memory
CCharPointer buffer = UnmanagedMemory.malloc(1024);
try {
// Use buffer...
} finally {
UnmanagedMemory.free(buffer);
}
// Pinned objects for native interop
byte[] javaArray = new byte[100];
try (PinnedObject pinned = PinnedObject.create(javaArray)) {
CCharPointer nativePtr = pinned.addressOfArrayElement(0);
// Pass nativePtr to native functions
}Cross-isolate communication via opaque object references.
/**
* Opaque representation of handles to Java objects
*/
interface ObjectHandle extends PointerBase {}
/**
* Management of ObjectHandle sets with global and local scopes
*/
class ObjectHandles {
/** Get the global object handles */
static ObjectHandles getGlobal();
/** Create a new local object handle set */
static ObjectHandles create();
/** Create a handle for the given object */
ObjectHandle create(Object object);
/** Get the object for the given handle */
<T> T get(ObjectHandle handle);
/** Destroy a handle */
void destroy(ObjectHandle handle);
/** Destroy all handles in this set */
void destroyAll();
}Usage Examples:
// Global handles (persist across calls)
ObjectHandles global = ObjectHandles.getGlobal();
ObjectHandle handle = global.create(myObject);
// Local handles (cleaned up automatically)
ObjectHandles local = ObjectHandles.create();
ObjectHandle localHandle = local.create(localObject);
// Cross-isolate communication
String retrieved = global.get(handle);Thread management, callback registration, and VM lifecycle control.
/**
* Thread management and callback registration
*/
class Threading {
/** Register a recurring callback */
static void registerRecurringCallback(long intervalNanos, Runnable callback);
/** Check if called from a thread that was attached externally */
static boolean isCurrentThreadVirtual();
}
/**
* VM initialization, shutdown, and diagnostic operations
*/
class VMRuntime {
/** Initialize the VM runtime */
static void initialize();
/** Shut down the VM */
static void shutdown();
/** Dump the heap to a file */
static void dumpHeap(String outputFile, boolean live);
}
/**
* Access to runtime options and configuration
*/
class RuntimeOptions {
/** Get a runtime option value */
static <T> T get(String optionName);
/** Set a runtime option value */
static <T> void set(String optionName, T value);
}Usage Examples:
// Recurring callbacks
Threading.registerRecurringCallback(1_000_000_000L, () -> {
System.out.println("Heartbeat");
});
// VM lifecycle
VMRuntime.initialize();
try {
// Application logic
} finally {
VMRuntime.shutdown();
}
// Heap diagnostics
VMRuntime.dumpHeap("/tmp/heap.hprof", true);
// Runtime options
String gcType = RuntimeOptions.get("gc.type");
RuntimeOptions.set("debug.enabled", true);Additional utilities for annotation access, process properties, and logging.
/**
* Runtime annotation access utilities
*/
class AnnotationAccess {
/** Check if annotation is present on element */
static boolean isAnnotationPresent(AnnotatedElement element, Class<? extends Annotation> annotation);
/** Get annotation from element */
static <T extends Annotation> T getAnnotation(AnnotatedElement element, Class<T> annotation);
}
/**
* Process property access
*/
class ProcessProperties {
/** Get process ID */
static long getProcessID();
/** Get executable path */
static String getExecutableName();
/** Get command line arguments */
static String[] getArgumentVector();
}
/**
* Custom logging support
*/
interface LogHandler {
/** Handle a log message */
void log(String message);
}Exception classes for diagnosing and handling registration-related errors in native images.
/**
* Exception thrown when reflection queries access unregistered elements
*/
class MissingReflectionRegistrationError extends Error {
/** Constructor with detailed information about the missing element */
MissingReflectionRegistrationError(String message, Class<?> elementType,
Class<?> declaringClass, String elementName,
Class<?>[] parameterTypes);
/** Get the type of element being queried (Class, Method, Field, Constructor) */
Class<?> getElementType();
/** Get the class on which the query was attempted */
Class<?> getDeclaringClass();
/** Get the name of the queried element or bulk query method */
String getElementName();
/** Get parameter types passed to the query */
Class<?>[] getParameterTypes();
}
/**
* Exception thrown when JNI queries access unregistered elements
*/
class MissingJNIRegistrationError extends Error {
/** Constructor with detailed information about the missing JNI element */
MissingJNIRegistrationError(String message, Class<?> elementType,
Class<?> declaringClass, String elementName,
String signature);
/** Get the type of element being queried (Class, Method, Field, Constructor) */
Class<?> getElementType();
/** Get the class on which the query was attempted */
Class<?> getDeclaringClass();
/** Get the name of the queried element */
String getElementName();
/** Get the JNI signature passed to the query */
String getSignature();
}Usage Examples:
// Handling reflection registration errors
try {
Method method = MyClass.class.getDeclaredMethod("unregisteredMethod");
} catch (MissingReflectionRegistrationError e) {
System.err.println("Missing reflection registration: " +
e.getElementName() + " in " + e.getDeclaringClass());
// Log for build-time registration
registerForNextBuild(e.getDeclaringClass(), e.getElementName());
}
// Handling JNI registration errors
try {
// JNI call that triggers registration check
nativeMethodCall();
} catch (MissingJNIRegistrationError e) {
System.err.println("Missing JNI registration: " +
e.getElementName() + " with signature " + e.getSignature());
// Handle gracefully or fail fast
}
// Prevention: Register elements at build time
// In a Feature class:
RuntimeReflection.register(MyClass.class.getDeclaredMethod("methodName"));
RuntimeJNIAccess.register(MyClass.class);These runtime core APIs provide the foundation for native image execution, enabling applications to manage memory, communicate across isolates, handle platform differences, and control runtime behavior while maintaining the performance benefits of ahead-of-time compilation.
Install with Tessl CLI
npx tessl i tessl/maven-org-graalvm-sdk--nativeimage