DelayQueue makes an element available to take only after its Delayed value reports a nonpositive remaining delay. Its capacity is unbounded unless the application adds admission control.
Java DelayQueue: release retry work only after its deadline
Operational contract
The ticket stores a monotonic nanoTime deadline and orders itself by that deadline. The scheduler rejects negative delays and accepts at most 47 seconds for this example. poll returns null when no ticket has expired, even if the queue contains future work. Distinct tickets sharing a deadline can be removed in either order; add a sequence field when tie order matters. The class itself has no backlog cap, so a service accepting untrusted retry volume needs a separate quota before scheduling.
Failure case
An order retry is due after 23 seconds. The worker calls poll after 8 seconds and receives no ticket. A missing result does not mean the queue is empty. When the deadline passes, the ticket becomes eligible; worker scheduling can still delay actual processing beyond the due time.
Java code
import java.time.Duration;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public class OrderRetryQueue {
private final DelayQueue<RetryTicket> retries = new DelayQueue<>();
public void schedule(String orderId, Duration delay) {
if (delay.isNegative() || delay.compareTo(Duration.ofSeconds(47)) > 0)
throw new IllegalArgumentException("Delay outside policy");
retries.offer(new RetryTicket(orderId, System.nanoTime() + delay.toNanos()));
}
public String pollDue() {
RetryTicket ticket = retries.poll();
return ticket == null ? null : ticket.orderId;
}
private static final class RetryTicket implements Delayed {
private final String orderId;
private final long deadline;
private RetryTicket(String orderId, long deadline) {
this.orderId = orderId;
this.deadline = deadline;
}
public long getDelay(TimeUnit unit) {
return unit.convert(deadline - System.nanoTime(), TimeUnit.NANOSECONDS);
}
public int compareTo(Delayed other) {
return Long.compare(deadline, ((RetryTicket) other).deadline);
}
}
}Performance and ownership cost
Insertion and eligible-head removal use heap-like O(log N) work for N tickets; checking an ineligible head is cheap. Memory is O(N), with no built-in ceiling. The short 47-second horizon keeps signed nanoTime subtraction and ordering within a practical window, but does not cap queued volume.
Common Mistakes
- Do not interpret poll returning null as an empty queue.
- Do not accept unlimited retry tickets into an unbounded queue.
- Do not use wall-clock time for elapsed-delay calculations.
Connected lessons
- Java blocking queues: bounded capacity and backpressure
- Java SynchronousQueue: a handoff has no storage capacity
- Java PriorityQueue: define tie order in the comparator
- Java Exchanger: swap buffers at a two-party rendezvous
- Java LinkedTransferQueue: wait until a consumer receives work
- Java concurrent state and handoff quiz
- Advanced Java
