BPF Maps¶
Blog series: Part 2 — Recording data in basic eBPF maps · Part 6 — Ring buffers in libbpf · Part 10 — Global variables
Javadoc: BPFHashMap · BPFArray · BPFRingBuffer · BPFPerCpuArray · BPFPerCpuHashMap
Source: BPFHashMap.java · BPFArray.java · BPFRingBuffer.java · BPFPerCpuArray.java · BPFPerCpuHashMap.java
See also: Ring Buffer · Global Variables · Map of Maps · Shared Maps · Tail Calls
Maps are the primary mechanism for sharing data between BPF programs and user-space Java code.
hello-ebpf provides typed Java wrappers for all major map types. Maps are declared as fields on
your @BPF class and annotated with @BPFMapDefinition.

Declaration pattern¶
@BPF(license = "GPL")
public abstract class MyProg extends BPFProgram {
@BPFMapDefinition(maxEntries = 1024)
final BPFHashMap<Integer, Long> counts = BPFHashMap.newInstance();
}
The compiler plugin generates the corresponding SEC(".maps") definition in C.
BPFHashMap¶
When to use: General-purpose key/value store. Lookups are O(1) average.
Map type: BPF_MAP_TYPE_HASH
Declaration:
@BPFMapDefinition(maxEntries = 10_000)
final BPFHashMap<@Unsigned Integer, Long> pidToCount = BPFHashMap.newInstance();
BPF-side API (inside @BPFFunction):
Ptr<Long> valPtr = pidToCount.bpf_get(pid); // returns Ptr<V> — may be null!
if (valPtr != null) {
valPtr.set(valPtr.val() + 1);
} else {
long zero = 0;
pidToCount.bpf_put(pid, zero);
}
pidToCount.bpf_delete(pid);
bpf_get returns Ptr<V>, not V
Always null-check the result of bpf_get. If the key is absent the pointer is null and
dereferencing it will crash the BPF verifier.
Java-side API:
prog.pidToCount.get(1234); // Optional<Long>
prog.pidToCount.put(1234, 99L);
prog.pidToCount.delete(1234);
prog.pidToCount.forEach((k, v) -> System.out.println(k + " -> " + v));
BPFLRUHashMap¶
When to use: Like BPFHashMap but automatically evicts least-recently-used entries when full.
Ideal for connection tracking or caches where stale entries are acceptable.
Map type: BPF_MAP_TYPE_LRU_HASH
Declaration:
@BPFMapDefinition(maxEntries = 65536)
final BPFLRUHashMap<Long, ConnInfo> connTable = BPFLRUHashMap.newInstance();
API is identical to BPFHashMap.
BPFArray¶
When to use: Fixed-size indexed array. All entries exist from creation (no null for missing entries). Great for per-index counters or lookup tables.
Map type: BPF_MAP_TYPE_ARRAY
Declaration:
BPF-side API:
// Index must be a constant or a verified variable in [0, maxEntries)
Ptr<Long> slot = histogram.bpf_get(index); // never null for arrays
if (slot != null) {
slot.set(slot.val() + 1);
}
Java-side API:
BPFRingBuffer¶
When to use: Low-overhead, variable-size event streaming from BPF to user-space. Prefer over perf event arrays for new code.
Map type: BPF_MAP_TYPE_RINGBUF
Declaration:
@BPFMapDefinition(maxEntries = 1 << 24) // size in bytes, must be power of 2
final BPFRingBuffer<Event> events = BPFRingBuffer.newInstance(Event.class);
BPF-side API:
Ptr<Event> e = events.reserve();
if (e != null) {
e.val().pid = BPFJ.currentPid();
e.val().tgid = BPFJ.currentTgid();
events.submit(e);
}
// Or discard: events.discard(e);
Java-side API:
prog.events.setCallback((event) -> System.out.println("pid=" + event.pid));
prog.consumeAndThrow(); // poll ring buffer (or prog.consumeAndSleep(intervalMs))
BPFPerCpuArray¶
When to use: Per-CPU counters. Each CPU has its own independent copy — no locking, maximum throughput. Aggregate values Java-side by summing across CPUs.
Map type: BPF_MAP_TYPE_PERCPU_ARRAY
Declaration:
@BPFMapDefinition(maxEntries = 1)
final BPFPerCpuArray<Long> pktCount = BPFPerCpuArray.newInstance();
BPF-side API: identical to BPFArray.
Java-side API:
List<Long> perCpu = prog.pktCount.getAll(0); // one value per CPU
long total = perCpu.stream().mapToLong(Long::longValue).sum();
BPFBloomFilter¶
When to use: Probabilistic membership test. Zero false negatives; small false-positive rate. Useful for quick rejection of known-bad IPs or processes.
Map type: BPF_MAP_TYPE_BLOOM_FILTER
Declaration:
@BPFMapDefinition(maxEntries = 10_000)
final BPFBloomFilter<Integer> blocklist = BPFBloomFilter.newInstance();
BPF-side API:
if (blocklist.peek(suspectIp)) {
// Probably in set — apply heavier check or drop
return XDP_DROP;
}
// Definitely not in set — pass
return XDP_PASS;
Java-side API:
BPFPerCpuHashMap¶
When to use: Per-CPU hash map for lock-free counters keyed by an arbitrary value. Each CPU has its own value slot; aggregate Java-side by summing across CPUs.
Map type: BPF_MAP_TYPE_PERCPU_HASH
Declaration:
@BPFMapDefinition(maxEntries = 4096)
final BPFPerCpuHashMap<Integer, Long> pidBytes = BPFPerCpuHashMap.newInstance();
BPF-side API: identical to BPFHashMap (bpf_get, bpf_put, bpf_delete).
Java-side API:
List<Long> perCpuValues = prog.pidBytes.getAll(pid); // one value per CPU
long total = perCpuValues.stream().mapToLong(Long::longValue).sum();
BPFHashOfMaps / BPFArrayOfMaps¶
When to use: A map whose values are themselves maps — e.g. per-CPU, per-connection, or per-user maps where each key needs its own independent state. See Map-of-Maps for full documentation.
Map types: BPF_MAP_TYPE_HASH_OF_MAPS / BPF_MAP_TYPE_ARRAY_OF_MAPS
Declaration:
@BPFMapDefinition(maxEntries = 1) // inner-map template
BPFHashMap<Long, Long> innerTemplate;
@InnerMap("innerTemplate")
@BPFMapDefinition(maxEntries = 256)
BPFHashOfMaps<Integer, BPFHashMap<Long, Long>> outer;
BPF-side API:
Ptr<BPFHashMap<Long, Long>> inner = outer.lookup(cpuId);
if (inner != null) {
inner.bpf_put(syscallNr, count + 1);
}
Java-side API:
prog.outer.register(cpuId, innerMapHandle); // insert inner map fd
prog.outer.get(cpuId); // retrieve fresh fd handle
BPFQueue¶
When to use: FIFO queue. BPF enqueues events; Java dequeues them. Simpler than ring buffer when variable-length records are not needed.
Map type: BPF_MAP_TYPE_QUEUE
Declaration:
BPF-side API:
Java-side API:
BPFStack¶
When to use: LIFO stack. Otherwise identical to BPFQueue.
Map type: BPF_MAP_TYPE_STACK
API mirrors BPFQueue; pop() returns the most-recently-pushed entry.
BPFProgArray¶
When to use: Tail calls — jump from one BPF program to another without returning. The array maps integer indices to loaded BPF programs.
Map type: BPF_MAP_TYPE_PROG_ARRAY
Declaration:
BPF-side API:
// In @BPFFunction
jumptable.bpf_tail_call(ctx, index);
// Execution continues here only if tail call fails (index out of range / map empty)
Java-side setup:
prog.jumptable.register(0, prog.getProgramByName("handle_ipv4"));
prog.jumptable.register(1, prog.getProgramByName("handle_ipv6"));
BPFTaskStorage¶
When to use: Per-task state in struct_ops (sched_ext) programs. The kernel owns the
lifecycle — entries are automatically removed when the task exits, no explicit delete needed.
Safer than a hash map keyed by PID because there are no stale entries and lookup is O(1).
Map type: BPF_MAP_TYPE_TASK_STORAGE
Kernel minimum: 5.11
Declaration:
@Type
record TaskCtx(long wakeupCount, long lastRunNs) {}
@BPFMapDefinition(maxEntries = 0) // maxEntries is ignored for task storage
BPFTaskStorage<TaskCtx> taskCtx;
BPF-side API:
// In @BPFFunction
Ptr<TaskCtx> ctx = taskCtx.bpf_get_or_create(p); // p is Ptr<task_struct>
if (ctx != null) {
ctx.val().wakeupCount += 1;
}
bpf_get_or_create returns a pointer to the task's storage, creating it (zeroed) if it
doesn't exist yet. No null check needed for the create path — it returns null only on OOM.
The Java side cannot iterate task storage (kernel constraint). It can only be used from BPF
programs attached to struct_ops entries.
Full example: sched/TaskStorageScheduler.java — FIFO scheduler with per-task wakeup counts.
See sched_ext guide §4 for a complete walkthrough.
Common patterns¶
Initialise a map entry atomically¶
// BPF side — safe increment even under concurrency
Ptr<Long> val = counts.bpf_get(key);
if (val == null) {
long zero = 1;
counts.bpf_put(key, zero);
} else {
// __sync_fetch_and_add via BPFJ if needed
val.set(val.val() + 1);
}
Iterate over a hash map from Java¶
prog.counts.forEach((k, v) -> {
System.out.printf("key=%d count=%d%n", k, v);
prog.counts.delete(k); // reset as we read
});
Examples¶
HashMapSample.java— BPFHashMap for syscall countingRingSample.java— BPFRingBuffer event streamingHelloArrayOfMaps.java— BPFArrayOfMaps / BPFHashOfMapsPerCpuInnerMapSample.java— per-CPU inner maps