Java Queue Interface
FIFO queue operations in Java with the Queue interface — offer, poll, peek — and its implementations.
Java Queue Interface
Queue<E> is Collection<E> plus the idea of a head. The head is the next element to be removed; everything else waits its turn behind it. The default semantics — and the one almost every implementation in the JDK uses — is FIFO: first in, first out. Producers offer items to the tail; consumers poll them from the head. This chapter is about the contract: the six methods, the two error-handling styles, the null-handling rule, and which implementation to reach for in which situation.
Two ways to do each thing — by design
Every queue operation comes in two flavours: one that throws on failure, and one that returns a special value. The Javadoc lays it out as a 3×2 grid:
| Operation | Throws on failure | Returns special value |
|---|---|---|
| Insert | boolean add(E e) (throws IllegalStateException if full) | boolean offer(E e) (returns false if full) |
| Remove | E remove() (throws NoSuchElementException if empty) | E poll() (returns null if empty) |
| Examine | E element() (throws NoSuchElementException if empty) | E peek() (returns null if empty) |
The "throws" column is the one inherited from Collection and List — it's familiar but inconvenient when the empty/full case is normal (consumer waiting for the next job, bounded queue rejecting an excess producer). The "returns special value" column was added for queues so you can write loops that don't rely on exceptions for control flow:
String item;
while ((item = queue.poll()) != null) {
process(item);
}Use the returns form by default. Reach for remove/element only when an empty queue at that point would be a genuine bug you'd want surfaced as an exception.
Null and Queue don't mix
Because poll() and peek() return null to mean "empty," allowing null as a real element is a recipe for ambiguity. Every general-purpose Queue in the JDK except LinkedList rejects null — ArrayDeque, PriorityQueue, ArrayBlockingQueue, all the concurrent queues. LinkedList lets you add a null, but if you do you've broken the contract: there's no longer a way to distinguish "head is null" from "queue is empty."
The rule: don't store null in a queue. Pick ArrayDeque over LinkedList and the language enforces it for you.
FIFO is the default — but not universal
Queue doesn't require FIFO. The interface defines a head and operations that pull from it; how the head is decided is up to the implementation. The two implementations in java.util that aren't FIFO are:
PriorityQueue— the head is always the smallest element (per natural order or aComparator). Insertions go where the heap dictates, not at the tail.- The blocking variants in
java.util.concurrent—PriorityBlockingQueue,DelayQueue, etc. — re-order by priority, deadline, etc.
Everything else (ArrayDeque, LinkedList, ArrayBlockingQueue, LinkedBlockingQueue, ConcurrentLinkedQueue) is FIFO.
Picking an implementation
For the single-threaded case:
ArrayDeque— the default. Circular array, no per-element allocation, fast. Use it asQueue<E>or asDeque<E>depending on whether you need access at both ends.LinkedList— works, also implementsList. Pick it only if you genuinely need both interfaces on the same object. The chapter on LinkedList covers the trade-offs.PriorityQueue— when you want "smallest waiting item next" rather than FIFO.
For the multi-threaded case (covered in detail in the concurrency part later — pointers only here):
ConcurrentLinkedQueue— lock-free FIFO. Unbounded.ArrayBlockingQueue— bounded, array-backed, blocksputwhen full andtakewhen empty. The classic producer/consumer queue.LinkedBlockingQueue— optionally bounded, linked-node form of the same.PriorityBlockingQueue— concurrentPriorityQueue. Unbounded.
Most application code uses one of the first four. The blocking queues are how you build worker pools and back-pressure.
What you can call on any Queue
Above the six head-specific methods, a Queue is also a Collection — so size, isEmpty, contains, iterator, forEach, stream, clear, toArray all work. Iteration order is the order in which elements would be polled for FIFO implementations (ArrayDeque, LinkedList), but for PriorityQueue the iterator order is not priority order — it visits the underlying heap array as-is. If you want elements in priority order, you have to drain the queue with poll until it's empty.
A worked example: producer/consumer in one thread
The program below uses an ArrayDeque as a FIFO queue to model a tiny print-job system: jobs arrive over time (we represent that by offering in batches), and the worker drains each batch with poll. The error-style pair is shown at the top so you can see exactly when each form fires.
A few things to read out of the run:
pollandpeekquietly returnednullwhen the queue was empty;removeandelementraised exceptions. Both behaviours are part of the contract — pick the one that matches whether "empty" is an error or just a state.offer(null)threw onArrayDeque. That's the implementation enforcing the rule the interface depends on.- The
PriorityQueuepolled10, 20, 30, 40— sorted order, not insertion order. SameQueueinterface, completely different head-selection rule.
What's next
The first non-FIFO implementation deserves its own chapter — PriorityQueue is a heap-backed queue where the head is always the smallest element. That's the basic ingredient of pretty much every "process the most-urgent task next" scheduler in the JDK.
Practice
Inside a loop you write `while ((job = queue.poll()) != null) { process(job); }`. If you instead used `queue.remove()`, what would change at the contract level?