Skip to content
GraalVM Native Image

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.15.1.jar:terminal-tty-3.15.1.jar:terminal-api-3.15.1.jar:readline-api-3.15.1.jar:terminal-detect-3.15.1.jar \
  com.example.MyApp

The --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.Signal handling and java.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:

PlatformProvider UsedMechanism
Linux/macOSExecPtyTerminalProviderSpawns stty/tty processes
WindowsWinSysTerminalProviderJNI via aesh-console.dll
Cygwin/MSYS2CygwinTerminalProviderPOSIX PTY via Cygwin

The FFM-based providers (FfmTerminalProvider on POSIX, FFM WinConsoleNative on Windows) are not available in native-image because GraalVM does not yet support registering FFM downcall descriptors in reachability metadata. The providers fail gracefully and fall through to the next available provider.

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 -am

Recommended JVM Flags

When running as a regular JAR (not native-image) on Java 22+, add:

--enable-native-access=ALL-UNNAMED

This 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 VersionStatus
GraalVM CE 23Supported
GraalVM CE 24Supported
GraalVM CE 25Supported

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-tty JAR is missing from the classpath
  • A custom native-image.properties file interferes with the UseServiceLoaderFeature

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 warning

Add --enable-native-access=ALL-UNNAMED to your JVM or native-image arguments.

Windows: MissingForeignRegistrationError

If you see this error in native-image on Windows, ensure you’re using aesh-readline 3.15+. Older versions used the FFM WinConsoleNative which requires downcall registration. Version 3.15+ uses the JNI version automatically.