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(); // AliceNote: 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()returnsnull— 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
| Action | Throws exception | Returns null / false | Meaning |
|---|---|---|---|
| Insert | add(e) | offer(e) | Put element into queue |
| Remove | remove() | poll() | Take and remove head |
| Look | element() | 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()); // AArrayDeque
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()); // topPriorityQueue
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()); // 30How 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 emailSimple 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()); // 1PriorityBlockingQueue
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()); // 30DelayQueue
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 emailSynchronousQueue
No storageA 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 callstake()take()waits until another thread callsput()- 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: HelloLinkedTransferQueue
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()returnsnullnow - 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()); // backD) 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()); // rightE) 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()); // 9Part 4
Differences — Quick Comparison
Use this table to see how the main queue types differ.
Comparison table
| Type | Order | Thread-safe? | Blocking? | Bounded? |
|---|---|---|---|---|
| LinkedList | FIFO | No | No | No |
| ArrayDeque | FIFO / LIFO | No | No | No |
| PriorityQueue | By priority | No | No | No |
| ArrayBlockingQueue | FIFO | Yes | Yes | Yes (fixed) |
| LinkedBlockingQueue | FIFO | Yes | Yes | Optional |
| PriorityBlockingQueue | By priority | Yes | Yes | No |
| DelayQueue | By delay time | Yes | Yes | No |
| SynchronousQueue | Hand-off | Yes | Yes | 0 capacity |
| ConcurrentLinkedQueue | FIFO | Yes | No | No |
| LinkedBlockingDeque | Both ends | Yes | Yes | Optional |
Part 5
Which Queue Should You Choose?
Start with your use case, then pick the matching type.
Decision guide
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.
