4 min read

How to handle java.nio.ClosedByInterruptException

Introduction

The ClosedByInterruptException is a checked exception, which occurs when a thread is interrupted while performing I/O operations on a channel.

In this guide, we'll dive deep into theClosedByInterruptException, exploring its causes, implications, and how to handle it in your Java applications.

What is ClosedByInterruptException?

ClosedByInterruptException is a subclass of AsynchronousCloseException and is thrown when a thread is interrupted while it is doing a blocking I/O operation on a channel that implements the InterruptibleChannel interface.

Below are some of the classes implementing the InterruptibleChannel interface.

  1. FileChannel
  2. SocketChannel
  3. ServerSocketChannel
  4. SelectableChannel
  5. DatagramChannel, and etc.

There are two primary scenarios where the ClosedByInterruptException is thrown.

  1. Thread is interrupted while I/O channel is already running.
  2. Channel started on an already interrupted thread.

Let's look at both these scenarios in details with some examples using FileChannel and SocketChannel.

Example 1: Thread interrupted while channel is running

Let's create a Runnable implementation which reads a simple text file using FileChannel and prints it.

class FileReaderTask implements Runnable {
        @Override
        public void run() {
            try (FileChannel channel = FileChannel.open(Paths.get("example.txt"), StandardOpenOption.READ)) {
                ByteBuffer buffer = ByteBuffer.allocate(8);
                while (channel.read(buffer) != -1) {
                    buffer.flip();
                    // Process the data in the buffer
                    System.out.println("Read: " + new String(buffer.array(), 0, buffer.limit()));
                    buffer.clear();
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

And we'll run it in a thread which is then interrupted after a 10 milliseconds timeout.

public static void main(String[] args) {
        // Start the file reader thread
        Thread readerThread = new Thread(new FileReaderTask());
        readerThread.start();

        // Give the FileReaderTask thread some time to start reading
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // Interrupt thread B
        readerThread.interrupt();

        try {
            readerThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

Output

If we execute the code, it will print few parts from the text file followed by the ClosedByInterruptException.

Read: placerat
Read:  ac impe
java.nio.channels.ClosedByInterruptException
	at java.base/java.nio.channels.spi.AbstractInterruptibleChannel.end(AbstractInterruptibleChannel.java:199)
	at java.base/sun.nio.ch.FileChannelImpl.endBlocking(FileChannelImpl.java:171)
	at java.base/sun.nio.ch.FileChannelImpl.read(FileChannelImpl.java:237)
	at App$FileReaderTask.run(App.java:35)
	at java.base/java.lang.Thread.run(Thread.java:833)

What happened?

  • We start FileReaderTask in a separate Thread and wait for 10 milliseconds before interrupting it with readerThread.interrupt().
  • The FileChannel started reading and printing data evident from the 'Read:' lines in our output.
  • The FileReader thread receives interruption and closes any InterruptibleChannel instances. In our case, this happens to be our FileChannel which was reading the text file.
  • The read operation ends prematurely and hence a ClosedByInterruptException is thrown.
  • The FileReaderTask thread catches the exception and can do as it pleases. In our example, we just printStackTrace.

Example 2: Channel started on an interrupted thread

We'll create a Runnable implementation which uses SocketChannel to read from a socket and prints it.

class SocketConnectorTask implements Runnable {
        @Override
        public void run() {
            try {
                // Check if the thread is already interrupted
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("Thread was interrupted before SocketChannel operation");
                }

                // Attempt to open a SocketChannel
                try (SocketChannel socketChannel = SocketChannel.open()) {
                    // Configure the channel
                    socketChannel.configureBlocking(true);

                    // Attempt to connect (this operation can be interrupted)
                    socketChannel.connect(new InetSocketAddress("example.com", 80));

                    // If connected, try to read (this would also throw ClosedByInterruptException if interrupted)
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int bytesRead = socketChannel.read(buffer);

                    System.out.println("Read " + bytesRead + " bytes");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

And we'll run it in a thread as below. But the catch is we interrupt the thread before even starting it.

public static void main(String[] args) {
        Thread socketThread = new Thread(new SocketConnectorTask());

        // Interrupt the thread before starting it
        socketThread.interrupt();

        socketThread.start();

        try {
            socketThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

Output

If we execute the code, it will throw ClosedByInterruptException before reading anything from the socket.

Thread was interrupted before SocketChannel operation

java.nio.channels.ClosedByInterruptException
	at java.base/java.nio.channels.spi.AbstractInterruptibleChannel.end(AbstractInterruptibleChannel.java:199)
	at java.base/sun.nio.ch.SocketChannelImpl.endRead(SocketChannelImpl.java:389)
	at java.base/sun.nio.ch.SocketChannelImpl.endConnect(SocketChannelImpl.java:792)
	at java.base/sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:867)
	at App$SocketConnectorTask.run(App.java:38)
	at java.base/java.lang.Thread.run(Thread.java:833)

What happened?

  • We start SocketConnectorTask in a separate Thread, and before it's started, we call the .interrupt() method.
  • In the SocketConnectorTask thread, we can see that the thread is already interrupted when we checked the .isInterrupted() flag. So any further operations involving InterruptibleChannel will result in ClosedByInterruptException.
  • When the socketChannel.connect() method is called, it immediately throws the ClosedByInterruptException and does not proceed with reading the socket.
  • The SocketConnectorTask thread catches the exception and can do as it pleases. In our example, we just printStackTrace.

Handling ClosedByInterruptException

Before looking at ways to handle the ClosedByInterruptException, do note that this is a perfectly expected exception and doesn't necessarily mean you have a bug in your code.

While working with an InterruptibleChannel, you should be aware that there is always a possibility of receiving this exception and you need to consider it while developing your solution.

There are a few steps you can take to handle the exception:

  • Catch and Handle: This helps you gracefully shutdown/cleanup any resources you need to manually handle/close.
try {
    // Perform I/O operation on an interruptible channel
} catch (ClosedByInterruptException e) {
    // Handle the exception and do necessary actions
}
  • Check for interruption: As we saw in Example 2, thread might already be in interrupted status, so it is good idea to check the interrupted status before continuing with our blocking channel operations.
if (Thread.currentThread().isInterrupted()) {
    // Thread is already interrupted
}
  • Using AsynchronousFileChannel: For scenarios where you need to avoid`this exception and can do your operation asynchronously, consider using AsynchronousFileChannel. This class does not throw ClosedByInterruptException and provides asynchronous I/O operations.
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, 
    StandardOpenOption.READ, StandardOpenOption.WRITE);

Additional Considerations

You may not be creating the threads yourselves but may still face ClosedByInterruptException while using other tools like Netty which can interrupt current threads due to various factors.

So expect this exception and handle it whenever you do any operation with a channel that implements InterruptibleChannel interface.

Conclusion

I hope this guide was helpful in resolving your issue.

By following the best practices outlined in this guide, you can write code that gracefully handles interruptions during I/O operations.