Java Academy Logo

Java Academy

Queue in Java — All Types Explained

Learn the Java Queue and Deque family in simple English. See every common queue type, how they differ, and when to use each one.

Part 1

What is a Queue?

A Queue is a collection that holds elements waiting to be processed.

Simple idea

Think of people standing in a line at a ticket counter. The first person who joins is the first person served. This is called FIFO — First In, First Out.

// People join at the back
queue.offer("Alice");
queue.offer("Bob");
queue.offer("Carol");

// First out is Alice
queue.poll(); // Alice

Note: not every queue is strict FIFO. A PriorityQueue serves the highest priority item first, not always the oldest item.

Queue vs Deque

Queue

Add at one end, remove from the other end (usually FIFO).

Deque

Double-ended queue. You can add and remove from both ends. Can work as a queue or a stack.

Part 2

Important Queue Methods

For each action, Java gives two methods. They do the same job, but react differently when something goes wrong.

Why two methods for the same action?

Example: you try to remove an item, but the queue is empty.

  • remove() throws an error (exception)
  • poll() returns null — no crash

So the second column is not a different feature. It is the same action, written in a way that does not throw an exception. That is why we call it the safer option for beginners.

Method pairs

ActionThrows exceptionReturns null / falseMeaning
Insertadd(e)offer(e)Put element into queue
Removeremove()poll()Take and remove head
Lookelement()peek()See head without remove

Tip: prefer offer, poll, and peek when the queue might be empty or full. Your program can check the result instead of crashing.

Queue<String> q = new LinkedList<>();
q.offer("Task-1");
q.offer("Task-2");

System.out.println(q.peek()); // Task-1 (still in queue)
System.out.println(q.poll()); // Task-1 (removed)
System.out.println(q.poll()); // Task-2
System.out.println(q.poll()); // null (empty)

Part 3

All Queue Types in Java

Java has many Queue and Deque classes. Some are for one thread. Some are for many threads. Some sort by priority.

A) Everyday queues (single-thread friendly)

LinkedList

Queue + Deque + List
  • Can work as a Queue, Deque, or List
  • FIFO when used as a Queue
  • Uses linked nodes (more memory than ArrayDeque)
  • Not thread-safe

Use when: You need a queue and also List features, or frequent insert/remove at both ends in older code.

Example

Queue<String> queue = new LinkedList<>();
queue.offer("A");
queue.offer("B");
queue.offer("C");
System.out.println(queue.poll()); // A

ArrayDeque

Best general Deque
  • Resizable array implementation
  • Faster than LinkedList for most queue/stack use
  • No capacity limit (grows as needed)
  • Null elements are not allowed
  • Not thread-safe

Use when: Most queue or stack needs in a single-threaded app. Preferred default Deque.

Example

Deque<String> deque = new ArrayDeque<>();
deque.offerLast("first");
deque.offerLast("second");
System.out.println(deque.pollFirst()); // first
// Also works as a stack:
deque.push("top");
System.out.println(deque.pop()); // top

PriorityQueue

Not strict FIFO
  • Orders elements by priority (natural order or Comparator)
  • Smallest/highest-priority item comes out first
  • Not fully sorted when you iterate
  • Not thread-safe
  • Null not allowed

Use when: Tasks, jobs, or events must be processed by priority, not by arrival time.

Example

PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(30);
pq.offer(10);
pq.offer(20);
System.out.println(pq.poll()); // 10 (smallest first)
System.out.println(pq.poll()); // 20
System.out.println(pq.poll()); // 30

How to define priority in PriorityQueue

Priority means: which item comes out first. You can define it in three simple ways.

1. Default priority (natural order)

For numbers, the smallest value has the highest priority. For strings, alphabetical order is used.

PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(5);
pq.offer(1);
pq.offer(3);
System.out.println(pq.poll()); // 1  (smallest first)

2. Reverse priority (largest first)

Use Comparator.reverseOrder() when a bigger number should come out first.

PriorityQueue<Integer> pq =
        new PriorityQueue<>(Comparator.reverseOrder());

pq.offer(5);
pq.offer(1);
pq.offer(3);
System.out.println(pq.poll()); // 5  (largest first)

3. Custom priority with your own class

For real apps, store objects and decide priority with a field (for example: 1 = high, 2 = medium, 3 = low).

class Task {
    String name;
    int priority; // smaller number = higher priority

    Task(String name, int priority) {
        this.name = name;
        this.priority = priority;
    }
}

PriorityQueue<Task> tasks = new PriorityQueue<>(
    (a, b) -> a.priority - b.priority  // compare by priority
);

tasks.offer(new Task("Send email", 3));
tasks.offer(new Task("Fix bug", 1));     // highest priority
tasks.offer(new Task("Write report", 2));

System.out.println(tasks.poll().name); // Fix bug
System.out.println(tasks.poll().name); // Write report
System.out.println(tasks.poll().name); // Send email

Simple rule: in (a, b) -> a.priority - b.priority, the smaller priority number comes out first. To reverse it, use (a, b) -> b.priority - a.priority.

B) Blocking queues (multi-thread / producer-consumer)

A BlockingQueue can wait when it is empty (no item to take) or full (no space to add). Perfect for one thread producing work and another consuming it.

ArrayBlockingQueue

Fixed size
  • Array-based BlockingQueue
  • Has a fixed capacity you set at creation
  • Fairness option available
  • Thread-safe

Use when: You want a bounded buffer (for example, max 100 tasks waiting).

Example

BlockingQueue<String> jobs = new ArrayBlockingQueue<>(3);
jobs.put("Job-1"); // waits if queue is full
jobs.put("Job-2");
String job = jobs.take(); // waits if empty → Job-1
System.out.println(job);

LinkedBlockingQueue

Optional bound
  • Linked-node BlockingQueue
  • Can be bounded or almost unbounded
  • Good for producer-consumer pipelines
  • Thread-safe

Use when: Default choice for many producer-consumer tasks.

Example

BlockingQueue<String> queue = new LinkedBlockingQueue<>();
queue.put("Task-A");
queue.put("Task-B");
System.out.println(queue.take()); // Task-A
System.out.println(queue.size()); // 1

PriorityBlockingQueue

Priority + blocking
  • Like PriorityQueue, but thread-safe and blocking
  • Unbounded (grows with memory)
  • Orders by priority

Use when: Many threads add jobs, and workers must take highest priority first.

Example

BlockingQueue<Integer> pbq = new PriorityBlockingQueue<>();
pbq.put(50);
pbq.put(10);
pbq.put(30);
System.out.println(pbq.take()); // 10
System.out.println(pbq.take()); // 30

DelayQueue

Time-based
  • Elements become available only after a delay
  • Implements Delayed interface
  • Thread-safe

Use when: Scheduled tasks, retries, timeouts, or delayed notifications.

Example

DelayQueue<DelayedTask> dq = new DelayQueue<>();
dq.put(new DelayedTask("Send email", 3)); // ready after 3 seconds
DelayedTask task = dq.take(); // waits until delay ends
System.out.println(task.getName()); // Send email

SynchronousQueue

No storage

A normal queue is like a shelf: you put items on it, and someone takes them later. A SynchronousQueue has no shelf. The giver and the taker must meet at the same time.

Important: SynchronousQueue needs at least two threads — one to put() and one to take(). With only one thread, put() waits forever because nobody is there to take the item.

Simple picture

Person A wants to give a package. Person B wants to receive it. There is nowhere to leave the package. So A waits until B arrives, then hands it over directly. Both people (threads) must be present.

  • It does not store items (size is always 0)
  • Needs multithreading (producer thread + consumer thread)
  • put() waits until another thread calls take()
  • take() waits until another thread calls put()
  • One item is passed straight from one thread to another

Use when: you want a direct hand-off between threads, with no waiting list of items.

Example

BlockingQueue<String> handoff = new SynchronousQueue<>();

// Thread 1: gives the item (waits for a taker)
new Thread(() -> {
    try {
        System.out.println("Giving item...");
        handoff.put("Hello");  // waits here until take() happens
        System.out.println("Item given");
    } catch (InterruptedException e) {}
}).start();

// Thread 2: takes the item (waits for a giver)
String msg = handoff.take();   // waits here until put() happens
System.out.println("Got: " + msg); // Got: Hello

LinkedTransferQueue

Transfer support
  • High-performance concurrent queue
  • Supports transfer() — try to hand item directly to a waiting consumer
  • Unbounded

Use when: Advanced producer-consumer designs that need transfer semantics.

Example

LinkedTransferQueue<String> tq = new LinkedTransferQueue<>();
tq.offer("buffered-item");     // normal offer
boolean waiting = tq.hasWaitingConsumer();
tq.transfer("direct-item");    // waits for a consumer
System.out.println(tq.poll()); // buffered-item (if still present)

C) Concurrent non-blocking queues

These queues are safe for many threads, but they do not wait. If the queue is empty, poll() returns null right away. The thread continues — it does not sleep or block.

Blocking queue

  • Empty? take() waits
  • Full? put() waits
  • Good when a worker must wait for the next job
  • Example: ArrayBlockingQueue

Concurrent non-blocking queue

  • Empty? poll() returns null now
  • No waiting — thread keeps running
  • Good when you check the queue and move on
  • Example: ConcurrentLinkedQueue

Simple difference: blocking = wait for an item. Non-blocking = try now, get null if nothing is there. Both can be used by many threads safely.

ConcurrentLinkedQueue

Lock-free Queue
  • Thread-safe, non-blocking
  • Uses CAS (compare-and-swap), not heavy locks
  • Unbounded
  • Does not block when empty — poll() returns null

Use when: Many threads share a queue and you do not need waiting/blocking.

Example

ConcurrentLinkedQueue<String> q = new ConcurrentLinkedQueue<>();
q.offer("A");
q.offer("B");
System.out.println(q.poll()); // A
System.out.println(q.poll()); // B
System.out.println(q.poll()); // null (empty, no waiting)

ConcurrentLinkedDeque

Lock-free Deque
  • Thread-safe double-ended queue
  • Add/remove from both ends concurrently
  • Non-blocking

Use when: Concurrent work-stealing style designs or both-end access from many threads.

Example

ConcurrentLinkedDeque<String> dq = new ConcurrentLinkedDeque<>();
dq.offerFirst("front");
dq.offerLast("back");
System.out.println(dq.pollFirst()); // front
System.out.println(dq.pollLast());  // back

D) Blocking deques

LinkedBlockingDeque

Blocking Deque
  • Thread-safe Deque with optional capacity
  • Supports blocking put/take from both ends
  • Useful for work-stealing and dual-end buffering

Use when: Producer-consumer where both ends of the queue are used.

Example

LinkedBlockingDeque<String> deque = new LinkedBlockingDeque<>(5);
deque.putFirst("left");
deque.putLast("right");
System.out.println(deque.takeFirst()); // left
System.out.println(deque.takeLast());  // right

E) Older / special types

Stack (legacy)

Prefer ArrayDeque
  • LIFO (Last In, First Out)
  • Extends Vector (synchronized, older design)
  • Not recommended for new code

Use when: Legacy code only. For new apps, use ArrayDeque as a stack.

PriorityQueue with Comparator

Custom order
  • You decide the priority rule
  • Example: longer string first, or higher score first

Use when: Natural order is not what you need.

// Custom priority: larger number first
PriorityQueue<Integer> maxHeap =
    new PriorityQueue<>(Comparator.reverseOrder());

maxHeap.offer(3);
maxHeap.offer(9);
maxHeap.offer(1);
System.out.println(maxHeap.poll()); // 9

Part 4

Differences — Quick Comparison

Use this table to see how the main queue types differ.

Comparison table

TypeOrderThread-safe?Blocking?Bounded?
LinkedListFIFONoNoNo
ArrayDequeFIFO / LIFONoNoNo
PriorityQueueBy priorityNoNoNo
ArrayBlockingQueueFIFOYesYesYes (fixed)
LinkedBlockingQueueFIFOYesYesOptional
PriorityBlockingQueueBy priorityYesYesNo
DelayQueueBy delay timeYesYesNo
SynchronousQueueHand-offYesYes0 capacity
ConcurrentLinkedQueueFIFOYesNoNo
LinkedBlockingDequeBoth endsYesYesOptional

Part 5

Which Queue Should You Choose?

Start with your use case, then pick the matching type.

Decision guide

Simple FIFO queue (one thread)→ ArrayDeque
Stack (LIFO) in new code→ ArrayDeque (push/pop)
Process by priority→ PriorityQueue
Producer + consumer with max size→ ArrayBlockingQueue
Producer + consumer, flexible size→ LinkedBlockingQueue
Priority jobs from many threads→ PriorityBlockingQueue
Run task after a delay→ DelayQueue
Many threads, no waiting needed→ ConcurrentLinkedQueue
Direct hand-off, no buffer→ SynchronousQueue

Complete beginner example

import java.util.ArrayDeque;
import java.util.Queue;

public class QueueDemo {
    public static void main(String[] args) {
        Queue<String> printJobs = new ArrayDeque<>();

        printJobs.offer("Invoice.pdf");
        printJobs.offer("Report.docx");
        printJobs.offer("Photo.png");

        while (!printJobs.isEmpty()) {
            String job = printJobs.poll();
            System.out.println("Printing: " + job);
        }
    }
}

Part 6

FAQ

Is LinkedList still good as a Queue?▼

It works, but ArrayDeque is usually better for queue and stack use. Prefer ArrayDeque in new code unless you need List features.

Why does PriorityQueue not print in sorted order?▼

PriorityQueue only guarantees the head is the next priority item. Iterating the whole queue is not a full sorted list.

What is the difference between poll() and remove()?▼

Both remove the head. poll() returns null if empty. remove() throws an exception if empty.

When do I need a BlockingQueue?▼

When one thread creates work and another thread consumes it, and you want waiting when the queue is empty or full.

Can Queue store null?▼

Some implementations allow null (like LinkedList). Many do not (ArrayDeque, PriorityQueue, most concurrent queues). Avoid null in queues.

Continue Learning