Skip to content

hello-ebpf

hello-ebpf is the first and only Java library for eBPF that lets you write both the kernel-side BPF program and the user-space control code entirely in Java — no C files, no Makefiles, no separate build step.

Annotate a class with @BPF, extend BPFProgram, and mark methods with @BPFFunction. The build toolchain translates those method bodies to C, compiles them with clang, and bundles the resulting .o into your jar. At runtime, BPFProgram.load(MyClass.class) loads the program via libbpf. Everything else — maps, ring buffers, global variables, scheduler policies — is plain Java.

Features

Annotation processor and compiler plugin pipeline

  1. You write a class like MyProgram extends BPFProgram.
  2. Methods annotated @BPFFunction are extracted and translated to C by the compiler plugin.
  3. The C code is compiled by clang at build time; the .o is stored as a jar resource.
  4. At runtime BPFProgram.load(MyProgram.class) reads the bundled .o and calls libbpf to load it into the kernel.
  5. Maps, ring buffers, and global variables are accessible from the Java side through a typed API.

BPF code runs in the kernel, not the JVM

@BPFFunction methods are compiled to BPF bytecode and verified by the Linux kernel before loading. They run under strict constraints that ordinary Java code does not: the BPF stack is limited to 512 bytes, dynamic heap allocation is not available, loops must be bounded, and the kernel verifier rejects any program it cannot prove safe. Runtime exceptions, garbage collection, and JVM reflection do not apply inside @BPFFunction methods. If the verifier rejects your program, hello-ebpf prints a human-readable diagnostic — see Diagnostics.

Prerequisites

Requirement Minimum version
Linux kernel 6.14
clang / llvm 19
libbpf-dev any recent
JDK 22
Privileges root or CAP_BPF + CAP_PERFMON + CAP_NET_ADMIN

Install the native dependencies on Debian/Ubuntu:

sudo apt install -y clang-19 llvm-19 libbpf-dev linux-headers-$(uname -r)

See Install & Prerequisites to add hello-ebpf to your Maven project.

Quick example: XDP drop every 3rd packet

import me.bechberger.ebpf.annotations.bpf.BPF;
import me.bechberger.ebpf.annotations.bpf.BPFFunction;
import me.bechberger.ebpf.bpf.BPFProgram;
import me.bechberger.ebpf.bpf.GlobalVariable;
import me.bechberger.ebpf.bpf.XDPContext;
import me.bechberger.ebpf.bpf.XDPHook;
import me.bechberger.ebpf.runtime.XdpDefinitions.xdp_action;

@BPF(license = "GPL")
public abstract class DropEveryThird extends BPFProgram implements XDPHook {

    /** Packet counter shared between BPF and Java. */
    final GlobalVariable<Long> packetCount = new GlobalVariable<>(0L);

    @Override
    @BPFFunction
    public xdp_action xdpHandlePacket(XDPContext ctx) {
        long count = packetCount.get() + 1;
        packetCount.set(count);
        // Drop every third packet
        if (count % 3 == 0) {
            return xdp_action.XDP_DROP;
        }
        return xdp_action.XDP_PASS;
    }

    public static void main(String[] args) throws Exception {
        try (DropEveryThird prog = BPFProgram.load(DropEveryThird.class)) {
            prog.xdpAttach();
            System.out.println("XDP program attached. Press Ctrl-C to stop.");
            while (true) {
                Thread.sleep(1000);
                System.out.println("Packets seen: " + prog.packetCount.get());
            }
        }
    }
}

Build and run:

mvn package
sudo java --enable-native-access=ALL-UNNAMED -cp target/myapp.jar DropEveryThird

Network interface

xdpAttach() attaches to all non-loopback interfaces that are up. To attach to a specific interface, look up its index (from ip link) and call xdpAttach(ifindex).

--enable-native-access

hello-ebpf uses the Panama foreign-function API to call libbpf. Pass --enable-native-access=ALL-UNNAMED on every java invocation to suppress the module-system warning (required on JDK 24+).

See also: XDP hook docs · BPF Maps · Global Variables

Quick example: custom Linux scheduler

hello-ebpf can replace the Linux CPU scheduler with pure Java code via sched_ext. Here is MinimalScheduler.java — the complete program, nothing omitted:

@BPF(license = "GPL")
@Property(name = "sched_name", value = "minimal_scheduler")
@Property(name = "timeout_ms", value = "10000")
public abstract class MinimalScheduler extends SchedulerBase implements Scheduler {

    // SchedulerBase.init() creates the shared dispatch queue automatically
    final DispatchQueue shared = DispatchQueue.attach(SHARED_DSQ_ID);

    @Override
    public void enqueue(Ptr<task_struct> p, long enq_flags) {
        // Put every task into the shared FIFO queue
        shared.insertScaled(p, EnqFlags.passThrough(enq_flags));
    }

    @Override
    public void dispatch(int cpu, Ptr<task_struct> prev) {
        // Each CPU pulls the next task from the shared queue
        shared.moveToLocal();
    }

    public static void main(String[] args) throws Exception {
        try (var program = BPFProgram.load(MinimalScheduler.class)) {
            program.runSchedulerLoop();
        }
    }
}
sudo ./run.sh MinimalScheduler

The scheduler runs until you press Ctrl-C, at which point the kernel falls back to the default scheduler. See sched_ext documentation for priority queues, per-CPU dispatch, and userspace scheduling policies.

History

The project started in December 2023 as an experiment: could Java become a first-class language for eBPF, without wrapping C or shelling out to a separate toolchain? The first prototype, published with Part 1 of the blog series, used a javac annotation processor to translate @BPFFunction method bodies to C. That core idea has stayed the same; the library has grown around it.

By mid-2024 the compiler plugin could handle maps, ring buffers, global variables, XDP, TC, kprobes, tracepoints, and uprobes. Part 11 added CO-RE support by auto-generating 13,000 Java wrapper classes from kernel BTF, making programs portable across kernel versions without recompilation.

The scheduler work began in Part 15 with a BPF-side sched_ext scheduler, and culminated in a fully userspace scheduler where the scheduling policy runs entirely in Java (Parts 16–18). The 20-part blog series documents the whole journey.

Samples

Ready-to-run programs in bpf-samples/:

Sample What it does
HelloWorld Print filenames on every openat2 call
HashMapSample Count openat2 calls per process using BPFHashMap
RingSample Stream filename+pid events to userspace via ring buffer
SyscallCounter Count all syscalls over 5 s with a global variable
XDPDropEveryThirdPacket Drop every third incoming packet at XDP
PacketCountByLength Histogram of packet lengths using XDP
TCDropEveryThirdOutgoingPacket Drop ~1/3 of outgoing packets via TC egress
TCFirewall Block ingress traffic on ports specified at runtime
Firewall Full XDP firewall with per-IP rules and ring-buffer event log
PacketLogger Log packets via XDP + TC to a ring buffer
KProbeMultiCounter One program attached to 20 syscall entries (kprobe.multi)
LSMDemo Observe file_open, bpf, socket_create LSM hooks
CGroupBlockHTTPEgress Block all cgroup egress HTTP traffic
TimerDemo Self-rearming 1-second BPF timer
TailCallDemo XDP tail calls via BPFProgArray
HelloCubicSample Register a TCP congestion algorithm via struct_ops
CPUProfiler CPU profiler with stack-trace symbolisation
FeatureProbeSample Print kernel version and feature-probe table
sched/MinimalScheduler Minimal FIFO sched_ext scheduler in Java
sched/RustlandFifoSample Minimal FIFO userspace scheduler (policy entirely in Java)

See the full samples index for all programs with hook types and descriptions.

Blog series

This project is accompanied by a 20-part blog series on mostlynerdless.de:

Part Topic
1 Hello World — first eBPF program in Java
2 eBPF maps — hash maps and counters
3 Perf event buffers
4 Tail calls and your first eBPF application
5 First steps with libbpf
6 Ring buffers
7 Auto-layouting structs
8 Generating C code from Java
9 XDP-based packet filter
10 Global variables
11 BTF and 13,000 generated Java classes
12 Write your eBPF application in pure Java
13 Packet logger with TC and XDP hooks
14 Lightning-fast firewall with Java & eBPF
15 Writing a Linux scheduler in Java
16 Controlling task scheduling from Java
17 Lottery scheduler with sched_ext
18 Lottery scheduler in pure Java (bpf_for_each)
19 Concurrency testing with custom schedulers
20 A scheduler controlled by sound

Project layout

bpf-processor/   # javac compiler plugin (Java to C translation)
bpf/             # runtime library (BPFProgram, map types, helpers)
annotations/     # @BPF, @BPFFunction, @Type, @Size, ...
samples/         # runnable sample programs

Documentation

Page Description
Cheatsheet Quick reference for annotations, maps, and helpers
Cookbook Recipes for the shapes the BPF verifier cares about
Feature Matrix Minimum kernel versions per feature
Maps BPF map types and Java API
Shared maps Sharing maps across cooperating BPF programs (@SharedFrom)
Helpers BPF helper functions
Global Variables GlobalVariable API
Tracepoints SEC("tp/...") programs
kprobes SEC("kprobe/...") and SEC("kretprobe/...") programs
Uprobes SEC("uprobe/...") and SEC("uretprobe/...") programs, ProbeContext
Profiling CPU profiler (CPUProfiler) and JVM GC pause tracer (JvmGcPauseTracer)
TC Traffic Control hook
XDP XDP hook
LSM BPF LSM hooks
sched_ext Custom Linux schedulers
Diagnostics Debugging and troubleshooting
Changelog Release notes

License

Apache 2.0 (Java side) / GPL 2.0 (generated BPF C code when license = "GPL").