W3docs

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:

OperationThrows on failureReturns special value
Insertboolean add(E e) (throws IllegalStateException if full)boolean offer(E e) (returns false if full)
RemoveE remove() (throws NoSuchElementException if empty)E poll() (returns null if empty)
ExamineE 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 nullArrayDeque, 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 a Comparator). Insertions go where the heap dictates, not at the tail.
  • The blocking variants in java.util.concurrentPriorityBlockingQueue, 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 as Queue<E> or as Deque<E> depending on whether you need access at both ends.
  • LinkedList — works, also implements List. 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, blocks put when full and take when empty. The classic producer/consumer queue.
  • LinkedBlockingQueue — optionally bounded, linked-node form of the same.
  • PriorityBlockingQueue — concurrent PriorityQueue. 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.

java— editable, runs on the server

A few things to read out of the run:

  • poll and peek quietly returned null when the queue was empty; remove and element raised 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 on ArrayDeque. That's the implementation enforcing the rule the interface depends on.
  • The PriorityQueue polled 10, 20, 30, 40 — sorted order, not insertion order. Same Queue interface, 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

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?