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¶
- XDP, TC, kprobe/fentry, tracepoint, uprobe, LSM hooks
- sched_ext: write a Linux CPU scheduler in Java, or run the policy entirely in userspace
- BPF maps: hash, array, ring buffer, per-CPU, task storage, map-of-maps, shared maps
- Global variables, tail calls, BPF arenas, timers, struct_ops, attach cookies
- CO-RE: compile once, run on any kernel with BTF
- Human-readable verifier diagnostics

- You write a class like
MyProgram extends BPFProgram. - Methods annotated
@BPFFunctionare extracted and translated to C by the compiler plugin. - The C code is compiled by clang at build time; the
.ois stored as a jar resource. - At runtime
BPFProgram.load(MyProgram.class)reads the bundled.oand calls libbpf to load it into the kernel. - 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:
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:
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();
}
}
}
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:
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").