Java PriorityQueue
Use the heap-based PriorityQueue in Java to retrieve elements in priority order, with natural or custom ordering.
Java PriorityQueue
PriorityQueue<E> is the JDK's heap. It's a Queue, but instead of FIFO the head is always the least element according to a Comparator (or natural order if you don't supply one). offer is O(log n), poll and peek from the head are O(log n) and O(1) respectively, and the underlying storage is a flat array — no node allocation per element. That makes it the right tool for scheduler-shaped problems: "always work on whatever is most urgent next."
What "head" means here
For a FIFO queue, the head is whoever arrived first. For a PriorityQueue, the head is whoever has the smallest value at this moment — not the smallest at insertion time:
peek()returns the current smallest.poll()removes the current smallest and rebalances; the nextpeekshows the new smallest.offer(x)inserts and rebalances; ifxis the new smallest, the nextpeekreturns it.
"Smallest" is decided by:
- The
Comparatoryou passed to the constructor, if any. - Otherwise, the element's natural order via
Comparable. If your elements aren'tComparableand you didn't pass aComparator, the first call that needs to compare throwsClassCastException.
There's no maximum-heap mode. If you want a max-heap, pass Comparator.reverseOrder() (or your custom comparator reversed) — that's the standard idiom and we use it in the example below.
Building one
// Natural order (Comparable elements).
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
// Reversed for max-heap behaviour.
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Comparator.reverseOrder());
// Custom comparator — by length, then alphabetically.
PriorityQueue<String> byLen = new PriorityQueue<>(
Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder()));
// Pre-sized + comparator (initial capacity must come first).
PriorityQueue<String> bigByLen = new PriorityQueue<>(1_000,
Comparator.comparingInt(String::length));
// From an existing collection.
PriorityQueue<Integer> pq = new PriorityQueue<>(List.of(40, 10, 30, 20));The single-arg constructor that takes a Collection is O(n) — it heapifies in bulk instead of n O(log n) offers. Useful when you already have all the data up front.
Iteration is NOT in priority order
This is the surprise most people hit once. Iterating a PriorityQueue walks the underlying heap array in whatever order the heap happens to store its nodes — not ascending order:
PriorityQueue<Integer> pq = new PriorityQueue<>(List.of(40, 10, 30, 20));
System.out.println(pq); // possibly [10, 20, 30, 40]
for (int x : pq) System.out.print(x + \" \"); // possibly: 10 20 30 40
// possibly: 10 40 30 20The print order is an implementation detail of the heap layout. If you want elements in priority order, you have to drain:
while (!pq.isEmpty()) System.out.print(pq.poll() + \" \"); // sortedBe aware of this when you forEach, stream, toArray, or just print the queue for debugging. The Stream returned by stream() is not sorted, and adding .sorted() to it requires the elements to be Comparable (or you can pass a comparator).
Mutating an element after offer breaks the heap
The priority is computed at the moment of insertion. If you put an element in and then mutate the field the comparator looks at, the heap is now in an invalid state — poll may return the wrong head, contains may miss, you lose the invariant. There is no update or decrease-key method.
The two workarounds:
- Use immutable elements — records with priority fields, or wrapper records like
record Task(int priority, String name) {}. Then there's nothing to mutate after insertion. - Remove and re-insert if you really need to change priority:
pq.remove(task)isO(n)(it searches), thenpq.offer(taskWithNewPriority)isO(log n).
For a small queue, remove-and-reinsert is fine. For "I am rewriting Dijkstra's algorithm and need fast decrease-key," a PriorityQueue is not the right tool — you want an indexed heap or a TreeSet of (priority, node) pairs.
null and thread safety
Same rules as the rest of Queue:
- No nulls.
pq.offer(null)throwsNullPointerException. - Not thread-safe. Concurrent access needs
PriorityBlockingQueue, thejava.util.concurrentcousin.
A worked example: a tiny task scheduler
The program below uses a PriorityQueue to schedule tasks by priority (lower number = more urgent). It also shows the max-heap idiom and the iteration-order surprise.
Reading the run:
toString()andforEachshowed the tasks in some order — not sorted. Don't use either for debugging "is the priority right?" — drain withpollto see the real order.- The bulk constructor produced a correctly-ordered heap in one go — that's the linear-time heapify path.
- The mutation block at the end is the foot-gun in concentrated form. We mutated the array's priority after offering it, the heap didn't get a chance to re-balance, and now
peekis lying. The fix is either to use an immutable wrapper or toremove-and-offerafter the change.
What's next
The next chapter covers the implementation you reach for when you want a FIFO queue or a stack or a deque — and the one the JDK Javadoc nudges you toward as the default for all three: ArrayDeque. It's the class doing most of the work behind the scenes in the previous two chapters' example code.
Practice
You print a `PriorityQueue<Integer>` after offering 40, 10, 30, 20. The output is `[10, 20, 30, 40]` and you conclude the queue 'is sorted'. A teammate says: 'Don't rely on that.' Why are they right?