A BlockingQueue coordinates producers and consumers, with operations that wait, time out or fail immediately when capacity or elements are unavailable.
Java blocking queues: bounded capacity and backpressure
Java 8+. The program uses only JDK classes and runs without a framework.
Capacity is an admission policy
An import worker cannot process an unlimited stream of jobs at once. An ArrayBlockingQueue with a capacity of two limits queued membership. offer returns false when the queue is full instead of silently buffering more work. The producer must then reject, retry within a budget or slow down.
The program uses immediate offer and poll calls so its overload behaviour is deterministic. put and take can wait indefinitely, while timed forms bound the waiting call. Choosing between them is part of the service’s latency and shutdown contract. A queue does not automatically decide what an HTTP endpoint should return when admission fails.
Capacity counts elements, not bytes. Two tiny IDs are cheap; two enormous payloads may still exceed the process’s memory budget. Store a bounded reference to separately controlled payload storage when jobs can be large. Include work already being processed when estimating total in-flight memory.
Completion and shutdown are separate signals
A queue becoming empty does not prove every accepted job completed. A consumer may already have removed a job and still be processing it. Track task completion separately when callers need an end-of-batch result. The executor lesson uses Future results for that boundary.
Blocking queues do not allow null elements, so poll returning null can represent an empty queue. Shutdown needs an explicit protocol: interruption, a separately tracked stop condition or a sentinel value with rules for every consumer. One sentinel for several consumers can leave the others waiting forever. Catching InterruptedException and continuing unconditionally can defeat an intended stop request.
Working program
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class ImportAdmission {
public static void main(String[] args) {
BlockingQueue<String> jobs = new ArrayBlockingQueue<>(2);
System.out.println(jobs.offer("manifest-41"));
System.out.println(jobs.offer("manifest-42"));
System.out.println(jobs.offer("manifest-43"));
System.out.println("processing=" + jobs.poll());
System.out.println(jobs.offer("manifest-43"));
System.out.println("queued=" + jobs.size());
}
}Output
true
true
false
processing=manifest-41
true
queued=2Cost and failure boundaries
ArrayBlockingQueue uses fixed-capacity storage, so its membership storage is O(c) for capacity c. Immediate queue operations have small fixed bookkeeping work, but coordination and contention affect elapsed time. Timed or waiting operations intentionally include waiting; treating that latency as an algorithmic constant hides the service behaviour.
The queue preserves FIFO element ordering, but a group of consumers can finish in a different order after taking jobs. If result order matters, attach sequence identifiers and define a reordering step. Test a full queue, an empty queue, rejected submission and shutdown while producers or consumers are waiting.
Common Mistakes
- Do not retry a rejected offer in a tight unbounded loop.
- Do not use null as an end-of-stream element.
- Do not confuse an empty queue with completed processing.
Connect the contracts
Compare the boundary explained in Single-owner queues with the assumptions made by this program.
Extend the tested workflow
Continue with Java Flow: request items and observe publisher completion.
