🌐 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 Challenges with JNI

Before the Foreign Function & Memory API, Java developers had limited options for accessing native input APIs:

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.1.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>