In earlier versions of Java (1.1 through Java 7), file input and output operations were typically performed using the
java.io package. This model was synchronous and blocking — once a thread requested data from a file, socket, or stream, it had to wait
until the operation completed before continuing.
Modern Java uses NIO (New Input/Output) — a set of APIs in the java.nio package that support non-blocking and
asynchronous I/O. NIO allows threads to request reads or writes, then continue performing other work while the operation completes in the background.
Traditional I/O relies on streams such as FileInputStream or BufferedReader, which block execution until data becomes available.
NIO introduces channels and buffers to improve scalability and performance, especially in concurrent or high-throughput environments.
A channel represents a connection to an I/O source (such as a file or socket), and buffers act as containers for the data being read or written.
The key feature of NIO is that channels can be placed into non-blocking mode. When a channel is non-blocking, read or write operations return immediately, even if no data is currently available.
The example below demonstrates how a worker thread can perform non-blocking file reading using FileChannel and Selector.
The thread submits the read request and continues performing other work without waiting for I/O completion.
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.Selector;
import java.nio.channels.SelectionKey;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Iterator;
public class NonBlockingFileReader implements Runnable {
private final Path filePath;
public NonBlockingFileReader(Path filePath) {
this.filePath = filePath;
}
@Override
public void run() {
try (FileChannel channel = FileChannel.open(filePath, StandardOpenOption.READ)) {
channel.configureBlocking(false);
Selector selector = Selector.open();
channel.register(selector, SelectionKey.OP_READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
int readyChannels = selector.select(1000); // timeout 1 second
if (readyChannels == 0) {
System.out.println(Thread.currentThread().getName() + " doing other work...");
continue; // no channels ready yet
}
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
keyIterator.remove();
if (key.isReadable()) {
FileChannel ch = (FileChannel) key.channel();
int bytesRead = ch.read(buffer);
if (bytesRead == -1) {
System.out.println("End of file reached.");
return;
}
buffer.flip();
System.out.println("Read: " + new String(buffer.array(), 0, bytesRead));
buffer.clear();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Thread readerThread = new Thread(new NonBlockingFileReader(Path.of("example.txt")));
readerThread.start();
System.out.println("Main thread continues doing other work...");
}
}
In this example:
configureBlocking(false) sets the channel to non-blocking mode.Selector monitors one or more channels for readiness events such as OP_READ or OP_WRITE.
Serialization allows Java objects to be converted into a byte stream and later reconstructed. However, this process
must be handled carefully to avoid security issues.
In modern Java, serialization vulnerabilities are mitigated by validation hooks, ObjectInputFilter, and the transition toward
record-based data transfer or JSON serialization (e.g., with Jackson or Gson).
A major concern is that malicious serialized data can expose private fields or create unauthorized object graphs.
To mitigate this, use ObjectInputFilter to restrict deserialization:
import java.io.*;
import java.util.*;
public class SecureDeserialization {
public static void main(String[] args) throws Exception {
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter("com.javadeploy.model.*;!*");
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("data.ser"))) {
ois.setObjectInputFilter(filter);
Object obj = ois.readObject();
System.out.println("Deserialized object: " + obj);
}
}
}
This code restricts deserialization to safe packages and prevents arbitrary class instantiation from untrusted sources.
The evolution from blocking I/O to java.nio reflects Java’s broader shift toward scalability and efficiency.
Modern applications should use non-blocking I/O where concurrency or large data throughput is required, and adopt safe serialization techniques to prevent security vulnerabilities. Understanding these technologies is fundamental to writing performant, secure, and maintainable enterprise applications.