platform-java supports deploying and managing native executable applications alongside JVM applications. This enables running GraalVM native images, compiled binaries (C, C++, Rust, Go), and other native executables within the platform's lifecycle management.
Native applications run as separate OS processes (not in the JVM), but are managed by platform-java with the same lifecycle controls as JVM applications. The platform handles process launching, monitoring, graceful shutdown, and output redirection.
Mark an application as a native executable by setting the nativeImage flag:
ApplicationDescriptor nativeApp = ApplicationDescriptor.builder()
.applicationId("my-native-app")
.mainClass("com.example.NativeApp") // Not used for native apps
.nativeImage(true)
.addClasspathEntry(URI.create("file:///path/to/executable"))
.build();Native applications support these configuration properties:
| Property | Description | Example |
|---|---|---|
native.executable.path |
Explicit path to the native executable | /usr/local/bin/myapp |
native.workdir |
Working directory for the process | /var/platform-java/apps/myapp |
native.args |
Command-line arguments to pass | --server --port=8080 |
native.env.* |
Environment variables (strip prefix) | native.env.DATABASE_URL=jdbc:... |
The platform resolves the native executable path in this order:
- Explicit property: If
native.executable.pathis set, use that path - First classpath entry: Otherwise, use the first URI from
classpathEntries
// Option 1: Explicit path
.property("native.executable.path", "/usr/local/bin/myapp")
// Option 2: Via classpath (first entry used)
.addClasspathEntry(URI.create("file:///usr/local/bin/myapp"))Deploy a GraalVM-compiled native image:
ApplicationDescriptor graalApp = ApplicationDescriptor.builder()
.applicationId("graal-http-server")
.name("High-Performance HTTP Server")
.nativeImage(true)
.addClasspathEntry(URI.create("file:///opt/apps/http-server"))
.property("native.workdir", "/var/apps/http-server")
.property("native.args", "--port 8080 --threads 10")
.property("native.env.JAVA_OPTS", "-Xmx512m")
.build();
manager.deploy(graalApp);
manager.start("graal-http-server");Deploy a Rust-compiled executable:
ApplicationDescriptor rustApp = ApplicationDescriptor.builder()
.applicationId("rust-worker")
.name("Rust Background Worker")
.nativeImage(true)
.property("native.executable.path", "/usr/local/bin/rust-worker")
.property("native.env.RUST_LOG", "info")
.property("native.env.WORKER_THREADS", "4")
.build();
manager.deploy(rustApp);
manager.start("rust-worker");When manager.start(applicationId) is called for a native application:
- Platform detects
descriptor.isNativeImage() == true - Resolves executable path from properties or classpath
- Creates working directory if needed
- Builds command with arguments and environment variables
- Launches process via
ProcessBuilder - Starts background thread to capture and log process output
- Sets state to
RUNNING
When manager.stop(applicationId) is called:
- Platform sends
SIGTERM(graceful shutdown signal) - Waits up to 10 seconds for process to exit
- If still running, sends
SIGKILL(force terminate) - Waits up to 5 seconds for force kill
- Sets state to
STOPPED
Process stdout/stderr is redirected to the platform's logging system:
[graal-http-server] Server listening on port 8080
[graal-http-server] Accepted connection from 192.168.1.100
[rust-worker] Processing job ID 12345
| Feature | JVM Applications | Native Applications |
|---|---|---|
| Execution | In-process (classloader) | Separate OS process |
| Startup Time | Slower (JVM warmup) | Faster (compiled binary) |
| Memory | Shared JVM heap | Isolated process memory |
| Thread Pool | Platform-provided ThreadPoolExecutor |
Application manages own threads |
| Message Bus | In-memory shared bus | IPC via sockets/HTTP (future) |
| Service Registry | In-process lookup | Network-based discovery (future) |
| Hot Reload | Classloader swap | Process restart |
| Resource Monitoring | JVM heap/threads | OS-level (cgroups, /proc) |
Native applications have limited access to platform features since they run outside the JVM:
- Lifecycle management (deploy, start, stop, undeploy)
- Process monitoring (PID, exit code, uptime)
- Output redirection to platform logs
- Environment variable injection
- Working directory configuration
- In-process thread pool sharing
- In-memory message bus (requires IPC)
- In-process service registry (requires network discovery)
- JVM-based resource monitoring (heap, threads)
- Hot code reload (requires process restart)
- Performance-Critical Applications: Low-latency, high-throughput workloads
- Fast Startup Required: GraalVM native images for microservices
- Lower Memory Footprint: Compiled binaries vs. JVM overhead
- Polyglot Platform: Run non-JVM languages (Rust, Go, C++) alongside Java
- Legacy Integration: Wrap existing native binaries in platform lifecycle
- Standard Java Applications: Spring Boot, Jakarta EE, typical Java apps
- Platform Integration: Need message bus, service registry, shared features
- Hot Reload Required: Update code without process restart
- Resource Sharing: Benefit from shared JVM heap and thread pools
Core component that manages native process lifecycle:
public class NativeProcessLauncher {
public Process launch(String applicationId,
ApplicationDescriptor descriptor,
Path workingDir) throws IOException;
public void stop(String applicationId,
Process process,
long gracefulTimeoutMs) throws InterruptedException;
}Native processes are tracked via ApplicationContextImpl:
// Get native process (if any)
Optional<Process> process = context.getNativeProcess();
// Check if process is alive
if (process.isPresent() && process.get().isAlive()) {
long pid = process.get().pid();
// ...
}Unit tests for native process support focus on configuration validation:
@Test
void testLaunchWithNonNativeDescriptorThrowsException() {
ApplicationDescriptor descriptor = ApplicationDescriptor.builder()
.applicationId("app1")
.nativeImage(false) // Not a native image
.build();
assertThrows(IllegalArgumentException.class, () -> {
launcher.launch("app1", descriptor, tempDir);
});
}Full integration tests require actual native executables and platform-specific tools.
- Inter-Process Communication: Enable native apps to use message bus via Unix sockets / gRPC
- Service Discovery: Network-based service registry for native processes
- Resource Limits: Apply cgroups/rlimit to native processes
- Health Checks: HTTP/TCP probes for native process health
- Restart Policies: Automatic restart on failure (like systemd)
- Signal Handling: Custom signals for graceful reload (SIGHUP)
- Container Deployment - Running applications in Docker/Podman/LXC
- Native Binaries - Loading native libraries (.so/.dll) in JVM apps
- Application Lifecycle - General lifecycle management