Skip to content

Java Queue: Methods, Implementations, and Examples

Five white passenger jets taxi in single file along a wet taxiway at dusk under a violet and orange sky

Since Java 5.0, the JDK contains the interface java.util.Queue and several queue implementations, which differ in various properties (bounded/unbounded, blocking/non-blocking, thread-safe/non-thread-safe).

In this article, I’ll introduce the Queue interface and its methods, compare the queue implementations of the JDK, and show you which one to use when.

Java Queue Class Hierarchy

Before I present the Java queue in detail, I would like to give an overview in the form of a UML class diagram:

Java Queue class hierarchy (UML class diagram): Queue extends Collection, BlockingQueue extends Queue, TransferQueue extends BlockingQueue; ConcurrentLinkedQueue and PriorityQueue implement Queue; LinkedBlockingQueue, ArrayBlockingQueue, PriorityBlockingQueue, DelayQueue, and SynchronousQueue implement BlockingQueue; LinkedTransferQueue implements TransferQueue; Deque and BlockingDeque extend Queue and BlockingQueue
Queue, BlockingQueue, and TransferQueue – the JDK implements them with eight classes

I will describe the BlockingQueue interface in the next part of the tutorial.

The concrete queue classes ConcurrentLinkedQueue, PriorityQueue, ArrayBlockingQueue, DelayQueue, LinkedBlockingQueue, PriorityBlockingQueue, and SynchronousQueue follow. Finally, I will explain the TransferQueue interface together with the LinkedTransferQueue.

You can jump to the corresponding parts at any time using the tutorial navigation on the right margin.

The interfaces Deque and BlockingDeque, drawn in gray, and their implementations are covered in the tutorial series on deques. Since Java 21, Deque also extends SequencedCollection.

I explain how Queue and Deque differ – and when you need which of the two – in the article Queue vs. Deque in Java.

Java Queue Methods

The Queue interface defines six methods for inserting, removing, and viewing elements. For each of the three queue operations “Enqueue”, “Dequeue”, and “Peek”, the interface defines two methods: one that throws an exception in case of an error and one that returns a special value (false or null).

Methods for Inserting into the Queue

First, a graphical overview of the enqueue methods:

Methods for inserting into a queue: add() and offer() insert at the tail
Methods for inserting into a queue

Queue.add()

This method is already defined in the Collection interface and inserts an element into the queue. On success, the method returns true. If a bounded (size-restricted) queue is full, this method throws an IllegalStateException.

Queue.offer()

offer(), like add(), adds an element to the queue and returns true on success. If a bounded queue is full, this method returns false instead of throwing an IllegalStateException.

Methods for Removing from the Queue

Also for the dequeue methods, first a graphical overview:

Methods for removing from a queue: remove() and poll() remove the element at the head
Methods for removing from a queue

Queue.remove()

remove() removes the element from the queue’s head. If the queue is empty, the method throws a NoSuchElementException.

Queue.poll()

poll(), too, removes the element at the head of the queue. Unlike remove(), the method does not throw an exception if the queue is empty but returns null.

Methods for Viewing the Head Element

And again, first an overview of methods:

Methods for viewing the queue’s head element: element() and peek() return the element at the head without removing it
Methods for viewing the queue’s head element – the element stays in the queue

Queue.element()

The element() method returns the element from the head of the queue without removing it from the queue. If the queue is empty, a NoSuchElementException is thrown.

Queue.peek()

Like element(), peek() also returns the head element without removing it from the queue. However, if the queue is empty, this method returns null, just like poll().

Queue Methods – Summary

The following table shows the six methods again grouped by operation and type of error handling:

In case of error: exceptionIn case of error: return value
Adding an element (enqueue):add(E e)offer(E e)
Removing an element (dequeue):remove()poll()
Viewing an element (peek):element()peek()

How to Create a Queue?

java.util.Queue is an interface. An interface cannot be instantiated because it only describes what methods a class offers but does not contain implementations of those methods.

What happens if you still try?

public class QueueTest {
  public static void main(String[] args) {
    Queue<Integer> queue = new Queue<>(); // <-- Don't do this!
  }
}

When trying to compile this code, you would see the following error message:

QueueTest.java:5: error: Queue is abstract; cannot be instantiated
    Queue<Integer> queue = new Queue<>(); // <-- Don't do this!
                           ^
1 error

Therefore, you must select one of the concrete queue implementations, e.g., ConcurrentLinkedQueue:

Queue<Integer> queue = new ConcurrentLinkedQueue<>();

The JDK offers eight classes that implement Queue but not Deque. They differ in thread safety, blocking behavior, and capacity – the next section compares them.

Queue Implementations in Java: Which One to Use?

In the table, each class name links to the part of this series in which that queue and its specific characteristics are described. I compare the four deque implementations in the article about the Deque interface.

For explanations of the terms blocking, non-blocking, fairness policy, bounded, and unbounded, see the article about the BlockingQueue interface.

ClassBase data structureThread- safe?Blocking/ non-blockingFairness policyBounded/ unboundedIterator type
ConcurrentLinkedQueueLinked listYes (optimistic locking through compare-and-set)Non-blocking—UnboundedWeakly consistent¹
PriorityQueueMin-heap (stored in an array)NoNon-blocking—UnboundedFail-fast²
LinkedBlockingQueueLinked listYes (pessimistic locking with two locks)BlockingNot availableBounded³Weakly consistent¹
ArrayBlockingQueueArrayYes (pessimistic locking with one lock)BlockingOptionalBoundedWeakly consistent¹
PriorityBlockingQueueMin-heap (stored in an array)Yes (pessimistic locking with one lock)Blocking (dequeue only)Not availableUnboundedWeakly consistent¹
DelayQueuePriority queueYes (pessimistic locking with one lock)Blocking (dequeue only)Not availableUnboundedWeakly consistent¹
SynchronousQueueLinked stack (non-fair) or linked queue (fair)Yes (optimistic locking through compare-and-set)BlockingOptionalNo capacityThe iterator is always empty.
LinkedTransferQueueLinked listYes (optimistic locking through compare-and-set)Blocking (only transfer and dequeue)Not availableUnboundedWeakly consistent¹

¹ Weakly consistent: All elements that exist when the iterator is created are traversed by the iterator exactly once. Changes that occur after this can, but do not need to, be reflected by the iterator.

² Fail-fast: The iterator throws a ConcurrentModificationException if elements are added to or removed from the queue during iteration.

³ If you don’t pass a capacity to the constructor, the bound is Integer.MAX_VALUE elements.

Using these characteristics, you can find the proper queue for each use case. For day-to-day use of general queue implementations, I make the following recommendations:

  • ArrayDeque for single-threaded applications.
  • ConcurrentLinkedQueue as a thread-safe, non-blocking, and unbounded queue.
  • ArrayBlockingQueue as a thread-safe, blocking, bounded queue if you expect low to medium contention between producer and consumer threads.
  • LinkedBlockingQueue as a thread-safe, blocking, bounded queue if you expect high contention between producer and consumer threads (best to test which implementation is more performant for your use case).

So the choice between ConcurrentLinkedQueue and LinkedBlockingQueue comes down to whether your threads should wait: ConcurrentLinkedQueue never blocks, is unbounded, and protects its linked list with compare-and-set. LinkedBlockingQueue lets producers wait in put() while it is full and consumers in take() while it is empty; it protects its linked list with two locks and is bounded.

Here is the process in the form of a decision tree:

Decision tree for the Java queue implementations: without thread safety ArrayDeque; thread-safe and non-blocking ConcurrentLinkedQueue; thread-safe, blocking, and bounded ArrayBlockingQueue for low to medium contention, LinkedBlockingQueue for high contention
Three questions decide which queue implementation to use: thread safety, blocking, and contention

Optimized MPMC, MPSC, SPMC, and SPSC Queues

All thread-safe queue implementations provided by the JDK can be used in multi-producer-multi-consumer environments. This means that one or more writing threads and one or more reading threads can access the JDK queues concurrently.

With special mechanisms, it is possible to optimize queues so that the overhead for maintaining thread safety is minimized when there is a restriction to one reading and/or one writing thread.

Accordingly, the following four cases are distinguished:

  • Multi-producer-multi-consumer (MPMC)
  • Multi-producer-single-consumer (MPSC)
  • Single-producer-multi-consumer (SPMC)
  • Single-producer-single-consumer (SPSC)

The open-source library JCTools provides highly optimized queue implementations for all four cases.

Example: How to Use a Queue?

The following example shows how to create a queue, fill it with some values, and retrieve the values. You can also find the example code on GitHub.

public class JavaQueueDemo {
  public static void main(String[] args) {
    // 1.
    Queue<Integer> queue = new ConcurrentLinkedQueue<>();

    // 2.
    for (int i = 1; i <= 5; i++) {
      queue.offer(i);
      System.out.println("queue.offer(" + i + ") --> queue = " + queue);
    }

    System.out.println();

    // 3.
    System.out.println("queue.peek() = " + queue.peek());

    System.out.println();

    // 4.
    while (!queue.isEmpty()) {
      System.out.println("queue.poll() = " + queue.poll() + " --> queue = " + queue);
    }

    System.out.println();

    // 5.
    System.out.println("queue.poll() = " + queue.poll());
    System.out.println("queue.peek() = " + queue.peek());
  }
}

The program does the following (the numbering refers to the comments in the source code):

  1. It creates a queue. Which one you use is irrelevant for this example since it doesn’t require any special queue properties. We will use ConcurrentLinkedQueue.
  2. Using Queue.offer(), we write the values 1 to 5 to the queue. And we display the queue’s content after each insertion.
  3. We look at the queue’s head element using Queue.peek().
  4. As long as the queue contains elements (we check this with the isEmpty() method, which the Queue interface inherits from Collection), we retrieve these elements with Queue.poll() and display them. After that, we show the entire content of the queue again.
  5. After the queue has been emptied, we once again display the return values of poll() and peek().

The program prints the following:

queue.offer(1) --> queue = [1]
queue.offer(2) --> queue = [1, 2]
queue.offer(3) --> queue = [1, 2, 3]
queue.offer(4) --> queue = [1, 2, 3, 4]
queue.offer(5) --> queue = [1, 2, 3, 4, 5]

queue.peek() = 1

queue.poll() = 1 --> queue = [2, 3, 4, 5]
queue.poll() = 2 --> queue = [3, 4, 5]
queue.poll() = 3 --> queue = [4, 5]
queue.poll() = 4 --> queue = [5]
queue.poll() = 5 --> queue = []

queue.poll() = null
queue.peek() = null

You can see very nicely how the elements are taken out in the same order as they were inserted (First-in-first-out – FIFO).

Summary and Outlook

In this part of the tutorial, you have learned about Java’s Queue interface and its methods, the queue implementations of the JDK, and when to use which one. Using an example, you have seen how to use the queue.

In the next part, we will look at the BlockingQueue interface. I will also explain the difference between bounded and unbounded or blocking and non-blocking queues.

After that, I’ll present each of the JDK’s queue implementations with its characteristics in detail.

If this article has helped you, I would greatly appreciate a positive review on my ProvenExpert profile. Your feedback helps me improve my content and motivates me to write new informative articles.

👉 Leave a review

Want Even More Knowledge?

My blog features many articles on Java, software architecture, and performance — from foundational concepts to advanced patterns.

If you want to go deeper, check out my trainings: hands-on, easy to understand, and directly applicable to your day-to-day project work. Instead of theory, I teach principles that help you write code that is better, more maintainable, and more performant in the long run.

Explore the Java Trainings

Become a Better Java Developer

My free newsletter keeps you ahead. Modern Java: new versions & features, performance, and JVM insights – once a month.

Search