Distributed concurrency toolkit for Java + PostgreSQL.
Three lock strategies, leader election, a PostgreSQL-backed message queue, and zero dependencies beyond org.postgresql:postgresql.
LockFactory<FencedLock> locks = Fencepost.Locks.session(dataSource).build(); locks.forName("orders").runLocked(token -> { externalStore.write(data, token.value()); // fencing token blocks stale writes });
Lock strategies
Pick the strategy that fits your workload. All three implement the same API.
PostgreSQL advisory locks. No table setup. Simplest option - just point at a DataSource and go.
SELECT ... FOR UPDATE with monotonic fencing tokens. Rejects stale writes from superseded holders.
Timestamp TTL with optional auto-renew. Releases the connection immediately - best for long-running work.
Leader election
Built on top of lease locks. One instance leads, the rest stand by. When the leader dies, a standby takes over.
Exactly one instance holds leadership at any time. Standby instances poll and take over when the lease expires or is released.
If the leader crashes or loses its lease, a standby acquires leadership within roughly one lease duration. No manual intervention.
React to leadership changes with onElected and onRevoked callbacks. Lock-free isLeader() check for hot paths.
Message queue
At-least-once delivery with visibility timeouts, delayed messages, and LISTEN/NOTIFY-based blocking dequeue.
Messages stay invisible during processing. Explicit ack() deletes the message; nack() makes it visible again immediately.
Uses PostgreSQL LISTEN/NOTIFY to wake consumers instantly when messages arrive. Falls back to polling.
Enqueue messages with a delay - they become visible only after the specified duration has elapsed.
Standalone publisher for when you only need to enqueue. Supports typed messages with headers and delayed delivery.
Publish messages within an existing JDBC transaction. Messages become visible only after the transaction commits.
Continuous message processing with auto-ack/nack, configurable concurrency, and error handling. Just provide a handler.
Observability
A built-in dashboard that streams the state of every lock and queue straight from PostgreSQL — no extra infrastructure to run.
Pushes updates over Server-Sent Events, driven by PostgreSQL LISTEN/NOTIFY as messages flow — with an automatic polling fallback if the stream drops.
Every lock's holder, fencing token, and status — held / quiet / free — plus per-queue depth: total, visible, in-flight, and oldest-message age, with drill-down to individual messages.
Runs on the JDK's built-in HTTP server. new FencepostDashboard(dataSource).start(), then open localhost:3388 — no web framework, no extra dependency.
fencepost is free, open-source, and maintained in spare time. If it saves you time, consider sponsoring to help keep it going.