# How to Fix Java InterruptedException: Proper Thread Interruption Handling
Your application hangs during shutdown:
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at com.myapp.service.ScheduledTask.run(ScheduledTask.java:45)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)InterruptedException is thrown when a thread is waiting, sleeping, or blocked and another thread interrupts it. This is Java's cooperative cancellation mechanism—but mishandling it can lead to hung threads, shutdown failures, and lost interrupts.
Understanding the Error
InterruptedException is thrown by blocking methods when the thread's interrupt status is set:
| Method | Behavior When Interrupted |
|---|---|
Thread.sleep() | Throws InterruptedException, clears interrupt status |
Object.wait() | Throws InterruptedException, clears interrupt status |
Thread.join() | Throws InterruptedException, clears interrupt status |
BlockingQueue.take() | Throws InterruptedException, clears interrupt status |
BlockingQueue.poll(timeout) | Throws InterruptedException, clears interrupt status |
Future.get() | Throws CancellationException if cancelled |
Critical: When InterruptedException is thrown, the thread's interrupt status is cleared. If you don't handle this properly, you lose the interruption signal.
The Wrong Way to Handle InterruptedException
Mistake 1: Swallowing the Exception
// BAD: Loses the interruption signal
public void run() {
while (running) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// Do nothing - BAD!
}
doWork();
}
}Mistake 2: Using Exception for Control Flow
// BAD: Using interruption as normal control flow
public void run() {
try {
while (true) {
Thread.sleep(1000);
doWork();
}
} catch (InterruptedException e) {
// Expected when done - not the right pattern
}
}Mistake 3: Catching and Ignoring in Libraries
// BAD: Library code should never swallow interruption
public void waitForCondition() {
try {
condition.await();
} catch (InterruptedException e) {
// Library swallowed it - BAD!
}
}Solutions
Solution 1: Restore the Interrupt Status
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(1000);
doWork();
} catch (InterruptedException e) {
// Restore the interrupt status
Thread.currentThread().interrupt();
break; // Exit the loop
}
}
cleanup();
}Solution 2: Propagate the Exception
```java // When you can terminate, propagate the exception public void performTask() throws InterruptedException { Thread.sleep(1000); // Let caller handle interruption doWork(); }
// In Runnable that doesn't support interruption public void run() { try { performTask(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // Handle cleanup } } ```
Solution 3: Proper Cancellation Pattern
```java public class CancellableTask implements Runnable { private volatile boolean cancelled = false; private final BlockingQueue<WorkItem> queue = new LinkedBlockingQueue<>();
public void cancel() { cancelled = true; // Interrupt to unblock any waiting operations Thread.currentThread().interrupt(); }
@Override public void run() { while (!cancelled && !Thread.currentThread().isInterrupted()) { try { WorkItem item = queue.poll(1, TimeUnit.SECONDS); if (item != null) { processItem(item); } } catch (InterruptedException e) { // Check if we should stop if (cancelled) { Thread.currentThread().interrupt(); break; } // Otherwise continue } } cleanup(); }
private void processItem(WorkItem item) throws InterruptedException { // Check for interruption during long operations for (int i = 0; i < item.getSteps(); i++) { if (Thread.currentThread().isInterrupted()) { throw new InterruptedException("Task cancelled"); } processStep(item, i); } } } ```
Solution 4: Using Future and ExecutorService
```java public class TaskExecutor { private final ExecutorService executor = Executors.newFixedThreadPool(4);
public Future<Result> submitTask(Callable<Result> task) { return executor.submit(task); }
public void shutdown() { executor.shutdown(); // Disable new tasks
try { // Wait for existing tasks to terminate if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { // Force shutdown if tasks don't complete List<Runnable> unfinished = executor.shutdownNow(); System.out.println(unfinished.size() + " tasks did not complete");
// Wait again for tasks to respond to cancellation if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { System.err.println("Executor did not terminate"); } } } catch (InterruptedException e) { // (Re-)Cancel if current thread also interrupted executor.shutdownNow(); Thread.currentThread().interrupt(); } } } ```
Solution 5: Implementing Interruptible Channels
```java public class InterruptibleFileProcessor { private volatile boolean interrupted = false;
public void processFile(Path file) throws IOException, InterruptedException { try (FileChannel channel = FileChannel.open(file, StandardOpenOption.READ)) { ByteBuffer buffer = ByteBuffer.allocate(8192);
while (channel.read(buffer) != -1) { if (Thread.currentThread().isInterrupted()) { throw new InterruptedException("Processing cancelled"); }
buffer.flip(); processBuffer(buffer); buffer.clear(); } } }
// Using interruptible I/O public void processFileInterruptibly(Path file) throws IOException { try (InterruptibleChannel channel = FileChannel.open(file, StandardOpenOption.READ)) { // If thread is interrupted, the channel will be closed // and a ClosedByInterruptException will be thrown processChannel(channel); } } } ```
Solution 6: Thread Pool with Proper Cancellation
```java public class ManagedTaskExecutor { private final ExecutorService executor; private final List<Future<?>> activeTasks = new ArrayList<>(); private final Object lock = new Object();
public ManagedTaskExecutor(int poolSize) { this.executor = Executors.newFixedThreadPool(poolSize); }
public Future<?> submit(Runnable task) { Future<?> future = executor.submit(() -> { try { task.run(); } finally { synchronized (lock) { activeTasks.remove(Future<?> current = ...); } } });
synchronized (lock) { activeTasks.add(future); }
return future; }
public void cancelAll() { synchronized (lock) { for (Future<?> future : activeTasks) { future.cancel(true); // true = interrupt if running } activeTasks.clear(); } }
public void shutdown() { cancelAll(); executor.shutdown();
try { if (!executor.awaitTermination(30, TimeUnit.SECONDS)) { executor.shutdownNow(); } } catch (InterruptedException e) { executor.shutdownNow(); Thread.currentThread().interrupt(); } } } ```
Common Scenarios
Producer-Consumer with Shutdown
```java public class ProducerConsumer implements AutoCloseable { private final BlockingQueue<Item> queue = new LinkedBlockingQueue<>(100); private final ExecutorService executor = Executors.newFixedThreadPool(2); private volatile boolean running = true;
public void start() { executor.submit(this::producer); executor.submit(this::consumer); }
private void producer() { try { while (running && !Thread.currentThread().isInterrupted()) { Item item = generateItem(); queue.put(item); // Can be interrupted } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
private void consumer() { try { while (running && !Thread.currentThread().isInterrupted()) { Item item = queue.poll(1, TimeUnit.SECONDS); if (item != null) { processItem(item); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { // Process remaining items Item item; while ((item = queue.poll()) != null) { processItem(item); } } }
@Override public void close() { running = false; executor.shutdownNow(); // Interrupt all threads
try { executor.awaitTermination(10, TimeUnit.SECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } ```
Long-Running Computation
```java public class LongRunningTask implements Callable<Result> { private volatile boolean cancelled = false;
public void cancel() { cancelled = true; }
@Override public Result call() throws InterruptedException { List<PartialResult> results = new ArrayList<>();
for (int i = 0; i < getTotalSteps(); i++) { // Check cancellation flag if (cancelled || Thread.currentThread().isInterrupted()) { throw new InterruptedException("Task cancelled at step " + i); }
// Also check during long operations PartialResult partial = computePartial(i); results.add(partial); }
return aggregateResults(results); }
private PartialResult computePartial(int step) throws InterruptedException { // For very long computations, check periodically for (int i = 0; i < 1000; i++) { if (Thread.currentThread().isInterrupted()) { throw new InterruptedException("Computation interrupted"); } // Do work... } return new PartialResult(); } } ```
Timeout with Interruption
```java public <T> T executeWithTimeout(Callable<T> task, long timeout, TimeUnit unit) throws TimeoutException, ExecutionException, InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor(); Future<T> future = executor.submit(task);
try { return future.get(timeout, unit); } catch (TimeoutException e) { future.cancel(true); // Interrupt the running task throw e; } finally { executor.shutdownNow(); } }
// Usage try { Result result = executeWithTimeout(this::longRunningTask, 30, TimeUnit.SECONDS); } catch (TimeoutException e) { System.out.println("Task timed out"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); System.out.println("Task was interrupted"); } ```
Verification Steps
- 1.Test thread interruption:
```java @Test void testTaskCancellation() throws Exception { AtomicBoolean completed = new AtomicBoolean(false); CountDownLatch started = new CountDownLatch(1);
Runnable task = () -> { started.countDown(); try { Thread.sleep(10000); completed.set(true); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } };
Thread thread = new Thread(task); thread.start(); started.await();
thread.interrupt(); thread.join(1000);
assertFalse(completed.get()); assertTrue(thread.isInterrupted() || !thread.isAlive()); } ```
- 1.Test executor shutdown:
```java @Test void testExecutorShutdown() throws Exception { ExecutorService executor = Executors.newSingleThreadExecutor();
Future<?> future = executor.submit(() -> { try { Thread.sleep(10000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } });
executor.shutdownNow(); assertTrue(executor.awaitTermination(2, TimeUnit.SECONDS)); assertTrue(future.isCancelled()); } ```
Key Takeaways
- Never swallow
InterruptedException—either restore the interrupt status or propagate the exception - Use
Thread.currentThread().interrupt()immediately after catchingInterruptedException - Check
Thread.currentThread().isInterrupted()periodically in long-running loops - Use
Future.cancel(true)to interrupt running tasks - Always shutdown
ExecutorServiceproperly with timeout andshutdownNow() - Implement
AutoCloseablefor resources that need cleanup - Design cancellation into your task interfaces from the start