GraalVM Native Image
Aesh Readline supports GraalVM native-image compilation, enabling fast startup and low memory footprint for CLI applications.
Quick Start
Build a native image with:
native-image \
--no-fallback \
--enable-native-access=ALL-UNNAMED \
-cp your-app.jar:readline-3.16.jar:terminal-tty-3.16.jar:terminal-api-3.16.jar:readline-api-3.16.jar:terminal-detect-3.16.jar \
com.example.MyAppThe --enable-native-access=ALL-UNNAMED flag is required to suppress warnings from the FFM (Foreign Function & Memory) API on Java 22+.
How It Works
The terminal-tty module includes GraalVM reachability metadata in META-INF/native-image/ that registers:
- Reflection config for
sun.misc.Signalhandling andjava.io.Console.isTerminal() - Resource config for
META-INF/services/(ServiceLoader) and native DLLs - JNI config for Windows console native methods
- Proxy config for
sun.misc.SignalHandler
No additional configuration is needed for most use cases.
Terminal Providers in Native Image
The Terminal Provider SPI discovers providers at runtime. In native-image mode:
| Platform | Provider Used | Mechanism |
|---|---|---|
| Linux/macOS (Java 22+, native access) | FfmTerminalProvider | FFM syscalls, no subprocesses |
| Linux/macOS (older Java or no native access) | ExecPtyTerminalProvider | Spawns stty/tty processes |
| Windows | WinSysTerminalProvider | Console API via WinConsoleNative (FFM on Java 22+, JNI via aesh-console.dll otherwise) |
| Cygwin/MSYS2 | CygwinTerminalProvider | POSIX PTY via Cygwin |
Providers are tried in priority order and decline gracefully when unsupported, so the first working provider wins. FFM downcall descriptors are registered in reachability metadata for GraalVM 25+, covering POSIX syscalls and the Windows kernel32 console functions. On older GraalVM versions the FFM paths are skipped and the fallbacks above apply without configuration.
Maven Profile for Native Tests
To run tests as native-image (validates reachability metadata):
<profile>
<id>native</id>
<activation>
<property>
<name>env.GRAALVM_HOME</name>
</property>
</activation>
<dependencies>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.11.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.11.4</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>1.1.2</version>
<extensions>true</extensions>
<executions>
<execution>
<id>test-native</id>
<goals><goal>test</goal></goals>
<phase>test</phase>
</execution>
</executions>
<configuration>
<buildArgs>
<arg>--enable-native-access=ALL-UNNAMED</arg>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</profile>Run native tests with:
GRAALVM_HOME=$JAVA_HOME mvn clean verify -pl terminal-tty -amRecommended JVM Flags
When running as a regular JAR (not native-image) on Java 22+, add:
--enable-native-access=ALL-UNNAMEDThis enables FFM-based terminal I/O which avoids spawning external stty processes. Without this flag, Java 24+ will block FFM access.
For Maven Surefire:
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>--enable-native-access=ALL-UNNAMED</argLine>
</configuration>
</plugin>Compatibility
| GraalVM Version | Status |
|---|---|
| GraalVM CE 23 | Supported |
| GraalVM CE 24 | Supported |
| GraalVM CE 25 | Supported |
Both Linux and Windows are supported. The CI pipeline tests native-image on both platforms.
Troubleshooting
ExternalTerminal instead of PosixSysTerminal
If your application gets ExternalTerminal (no raw mode, no tab completion), the ServiceLoader provider discovery failed. This can happen if:
- The
terminal-ttyJAR is missing from the classpath - A custom
native-image.propertiesfile interferes with theUseServiceLoaderFeature
Verify with logging:
native-image -Djava.util.logging.config.file=logging.properties ...With a logging.properties that enables FINE on org.aesh.terminal.tty.TerminalBuilder.
FFM Warnings on Java 22+
WARNING: java.lang.foreign.Linker::downcallHandle has been called ...
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warningAdd --enable-native-access=ALL-UNNAMED to your JVM or native-image arguments.
Windows: MissingForeignRegistrationError
If you see this error in native-image on Windows, the kernel32 downcall descriptors were not registered for the image. The descriptors ship in terminal-tty reachability metadata, so this usually means build configuration stripped META-INF/native-image from the classpath (for example an over-broad shading or resource-exclusion rule). Verify the metadata reaches the image build; no version-specific workaround applies.