Cross-platform Java input handling without JNI

How input4j leverages the Foreign Function & Memory API for modern, dependency-free input handling

Updated Mar 4, 2026 15 min read Advanced

Traditional Java Input Approaches

Before Java 22, developers had limited options for native input handling in Java:

Approach Pros Cons
Java AWT Robot Simple API Limited to basic input
JNI Full native access Complex setup, platform-specific
Third-party libs Pre-built solutions Additional dependencies
Java (Traditional JNI)
// Traditional JNI approach
public class NativeInput {
    static {
        System.loadLibrary("native-input");
    }
    
    private native void init();
    private native int getButtonCount();
    private native boolean isButtonPressed(int button);
    
    public static void main(String[] args) {
        NativeInput input = new NativeInput();
        input.init();
        // ... complex native code required
    }
}

The Foreign Function & Memory API

The Foreign Function & Memory API (FFM API), introduced in Java 22, provides a modern, safe way to interact with native code:

Type Safety

Compile-time checking of native function signatures

Memory Safety

Automatic memory management and bounds checking

Performance

Direct memory access without JNI overhead

No Native Artifacts

No .dll, .so, or .dylib files required

Java (FFM API)
// Modern FFM API approach
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.SymbolLookup;

public class ModernInput {
    private static final SymbolLookup LOOKUP = SymbolLookup.loaderLookup();
    
    public void init() {
        MemorySegment lib = LOOKUP.lookup("XInputGetState")
            .orElseThrow()
            .downcallHandle(
                MethodType.methodType(int.class, int.class, MemorySegment.class)
            );
        // ... safe, type-checked native calls
    }
}

Platform Support in input4j

input4j uses the FFM API to provide unified input handling across platforms:

Windows

DirectInput & XInput via native APIs

DirectInput XInput

Linux

evdev interface for input events

evdev /dev/input

macOS

IOKit HID device access

IOKit HID

Performance Comparison

input4j's FFM API approach offers significant performance advantages:

JNI
70ms
FFM API
25ms
Traditional Java
100ms
JNI
FFM API
Traditional Java

Implementation Details

input4j's architecture demonstrates best practices for FFM API usage:

Application Layer
Your Java Game/Application
input4j API
Unified Input API
Platform Adapters
FFM API Native Calls
Native APIs
DirectInput, XInput, evdev, IOKit
Java (input4j Architecture)
public class InputDevices {
    private static final Map<String, InputDevice> devices = new ConcurrentHashMap<>();
    
    public static InputDevice getDevice(String id) {
        return devices.computeIfAbsent(id, InputDevices::createDevice);
    }
    
    private static InputDevice createDevice(String id) {
        // Platform-specific implementation using FFM API
        return switch (Platform.getCurrent()) {
            case WINDOWS -> new WindowsInputDevice(id);
            case LINUX -> new LinuxInputDevice(id);
            case MACOS -> new MacOSInputDevice(id);
            default -> throw new UnsupportedOperationException();
        };
    }
}

Getting Started

Ready to use input4j in your project? Here's how to get started:

1

Add Dependency

dependencies {
    implementation 'de.gurkenlabs:input4j:1.3.1'
}
2

Initialize Devices

try (var devices = InputDevices.init()) {
    for (var device : devices.getAll()) {
        System.out.println("Found: " + device.getName());
    }
}
3

Handle Input

device.onButtonPressed(XInput.X, () -> 
    System.out.println("X button pressed"));

device.onAxisChanged(Axis.AXIS_X, value -> 
    System.out.println("X axis: " + value));

</div>