File manipulation—copying and moving files—is a common requirement in many Java applications, whether for backup, organization, or processing workflows. Java provides multiple APIs to accomplish these tasks: the traditional I/O streams (FileInputStream
/FileOutputStream
) and the modern NIO (java.nio.file.Files
) utilities introduced since Java 7.
This tutorial covers both approaches with practical examples, explaining their differences, advantages, and use cases.
Before Java 7, the common way to copy a file was to open input and output streams and manually transfer bytes. This approach gives you low-level control but requires careful resource management and more code.
FileInputStream
and FileOutputStream
import java.io.*;
public class FileCopyTraditional {
public static void copyFile(File source, File destination) throws IOException {
try (FileInputStream fis = new FileInputStream(source);
FileOutputStream fos = new FileOutputStream(destination)) {
byte[] buffer = new byte[8192]; // 8KB buffer
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
}
}
public static void main(String[] args) {
File src = new File("source.txt");
File dest = new File("destination.txt");
try {
copyFile(src, dest);
System.out.println("File copied successfully using traditional IO.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileInputStream
reads raw bytes from the source file.FileOutputStream
writes bytes to the destination file.buffer
temporarily stores chunks of bytes during transfer, improving efficiency.try-with-resources
statement ensures streams are closed automatically, avoiding resource leaks.Moving a file by streams requires copying the file contents and then deleting the original file:
import java.io.*;
public class FileMoveTraditional {
public static void moveFile(File source, File destination) throws IOException {
copyFile(source, destination); // Copy the file
if (!source.delete()) { // Delete the original file
throw new IOException("Failed to delete original file: " + source.getAbsolutePath());
}
}
// Reuse copyFile from previous example
public static void copyFile(File source, File destination) throws IOException {
try (FileInputStream fis = new FileInputStream(source);
FileOutputStream fos = new FileOutputStream(destination)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
}
}
public static void main(String[] args) {
File src = new File("source.txt");
File dest = new File("moved.txt");
try {
moveFile(src, dest);
System.out.println("File moved successfully using traditional IO.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
java.nio.file.Files
Java 7 introduced the NIO.2 package (java.nio.file
), which simplifies file operations with the Files
utility class. It provides methods such as Files.copy()
and Files.move()
, which are easier to use, safer, and more powerful.
Files.copy()
import java.io.*;
import java.nio.file.*;
public class FileCopyNIO {
public static void main(String[] args) {
Path source = Paths.get("source.txt");
Path destination = Paths.get("destination.txt");
try {
Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING);
System.out.println("File copied successfully using NIO.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Files.copy()
copies the file content atomically when supported by the file system.
The third argument is a varargs of CopyOption
s:
StandardCopyOption.REPLACE_EXISTING
overwrites the destination file if it exists.COPY_ATTRIBUTES
(copy file metadata).Files.copy()
can also copy directories recursively with additional logic.
It automatically handles resource management and buffering internally.
Files.move()
import java.io.*;
import java.nio.file.*;
public class FileMoveNIO {
public static void main(String[] args) {
Path source = Paths.get("source.txt");
Path destination = Paths.get("moved.txt");
try {
Files.move(source, destination, StandardCopyOption.REPLACE_EXISTING);
System.out.println("File moved successfully using NIO.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Files.move()
moves or renames a file.Files.copy()
, e.g., REPLACE_EXISTING
.Feature | Traditional IO (FileInputStream / FileOutputStream ) |
NIO (Files.copy() , Files.move() ) |
---|---|---|
Simplicity | Verbose and manual | Simple and concise |
Performance | Buffer size configurable but requires manual code | Optimized, atomic operations where supported |
Resource management | Must manage streams explicitly | Automatic and safer |
Atomic moves (rename) | Not supported | Supported if file system allows |
Metadata copying | Not handled | Supported (COPY_ATTRIBUTES option) |
Portability and modern API | Legacy, less portable | Modern, recommended since Java 7 |
Error handling | More complex to handle all corner cases | Robust and consistent |
Files.copy()
and Files.move()
. These methods are simpler, safer, and usually more efficient.StandardCopyOption.REPLACE_EXISTING
to control behavior explicitly.Copying and moving files in Java can be done either with low-level stream-based APIs or the modern NIO utilities. While traditional IO is still valid, NIO’s Files
API is the recommended approach for most use cases, offering simplicity, reliability, and enhanced functionality.
By mastering both, you’ll be equipped to handle file operations in any Java environment, ensuring your applications manipulate files efficiently and correctly.
Monitoring file system changes—such as file creation, modification, or deletion—is a common need in many applications, including logging systems, IDEs, synchronization tools, and auto-reloaders. Java’s NIO.2 API, introduced in Java 7, provides a powerful and efficient way to watch file system events through the WatchService
API.
This guide walks you through implementing a file watcher step-by-step, explaining key concepts, and providing a complete, well-commented Java example.
WatchService
APIThe WatchService
API allows you to monitor one or more directories for changes such as:
The API works by:
Path
) with a WatchService
.You create a WatchService
from the file system's default provider:
WatchService watchService = FileSystems.getDefault().newWatchService();
This service will be used to register directories and poll for events.
You register a Path
representing a directory with the WatchService
, specifying which event kinds to watch:
Path dir = Paths.get("path/to/directory");
dir.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);
You can register multiple directories if needed.
Events are retrieved as WatchKey
instances from the WatchService
. You can use blocking or non-blocking polling:
watchService.take()
blocks until an event occurs.watchService.poll()
returns immediately, possibly returning null
if no event is available.Each WatchKey
contains one or more WatchEvent
s, each describing an event kind and the affected file relative to the registered directory.
import java.io.IOException;
import java.nio.file.*;
import static java.nio.file.StandardWatchEventKinds.*;
import java.util.List;
public class DirectoryWatcher {
public static void main(String[] args) {
// Directory to monitor
Path dir = Paths.get("watched-dir");
// Create the watcher service
try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
// Register the directory for CREATE, DELETE, and MODIFY events
dir.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
System.out.println("Watching directory: " + dir.toAbsolutePath());
// Infinite loop to wait and process events
while (true) {
WatchKey key;
try {
// Wait for a key to be signaled (blocking call)
key = watchService.take();
} catch (InterruptedException e) {
System.out.println("Interrupted. Exiting.");
return;
}
// Retrieve all pending events for the key
List<WatchEvent<?>> events = key.pollEvents();
for (WatchEvent<?> event : events) {
// Get event kind
WatchEvent.Kind<?> kind = event.kind();
// The context for directory entry event is the relative path to the file
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path filename = ev.context();
System.out.printf("Event kind: %s. File affected: %s%n", kind.name(), filename);
// You can add custom logic here, e.g. react to specific files
}
// Reset the key — this step is critical to receive further watch events
boolean valid = key.reset();
if (!valid) {
System.out.println("WatchKey no longer valid, directory might be inaccessible.");
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
WatchService Creation: We create a new WatchService
instance from the default filesystem.
Registering the Directory: The dir.register()
method registers the directory and specifies the event kinds to monitor: create, delete, and modify.
Event Loop: The while(true)
loop calls watchService.take()
, which blocks until an event is available.
Processing Events: Each WatchKey
may have multiple events; we iterate over them. Each event provides:
ENTRY_CREATE
, ENTRY_MODIFY
, or ENTRY_DELETE
).Resetting the Key: Calling key.reset()
re-enables the key for further event notifications. If it returns false
, the key is invalid, possibly because the directory was deleted or is inaccessible.
Exception Handling: IO exceptions and interruptions are handled gracefully.
Only directories can be registered. You cannot register individual files. To monitor multiple directories, register each one separately.
Events are relative paths. The event’s context()
is relative to the directory registered, so combine it with the directory path if you need the full path.
WatchService behavior is platform-dependent. Some platforms may coalesce multiple events or behave slightly differently. For example, on Linux with inotify
, modify events may trigger multiple times.
Long-running processes should handle interruptions gracefully. When shutting down the watcher thread, interrupting the thread waiting on take()
is the recommended way to stop.
Performance considerations: For directories with heavy changes, events may be lost or coalesced. Monitor carefully for missed changes if that matters.
File renames: A rename may appear as a delete event followed by a create event.
You can extend the watcher to:
Path
with the same WatchService
.System.out.println
for production use.WatchService
API provides an efficient way to monitor directories for file creation, modification, and deletion.WatchService
, register directories with it, and then loop, waiting for events.WatchKey
and calling reset()
is essential to continue receiving events.By following this guide and using the example code, you can implement your own directory monitoring solution in Java that responds in near real-time to file system changes.
Traditional Java networking with ServerSocket
and Socket
classes uses blocking IO, where a thread waits for each client. While simple, this doesn’t scale well with many concurrent clients because each connection consumes a thread.
Java NIO (New IO) introduced non-blocking IO and the selector pattern to handle many connections efficiently with a small number of threads. This tutorial walks you through creating a minimal HTTP server using NIO’s ServerSocketChannel
, SocketChannel
, and Selector
.
ServerSocketChannel
in non-blocking mode.Selector
to multiplex multiple client connections.Non-blocking IO
In non-blocking mode, read/write calls on channels do not block if the data is not immediately available. Instead, they return immediately with how much data was read or written. This means a single thread can:
A Selector allows a single thread to monitor multiple channels for IO events:
select()
method blocks until one or more registered channels are ready for any of the requested operations.select()
again.ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8080));
serverChannel.configureBlocking(false); // Non-blocking mode
Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
The server runs an event loop, calling selector.select()
to wait for events, then handling each ready channel accordingly.
When the server socket channel is ready to accept, you call accept()
, configure the new socket channel as non-blocking, and register it with the selector for read events.
When a socket channel is ready for reading, you read bytes into a buffer. For simplicity, we assume the request fits into one read. (In production, you'd accumulate and parse incrementally.)
We parse the request line (e.g., GET / HTTP/1.1
), ignore headers for simplicity, and respond with a simple HTTP 200 OK message with a plain text body.
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;
public class SimpleNIOServer {
private static final int PORT = 8080;
private static final String RESPONSE_BODY = "Hello from NIO HTTP Server!";
private static final String RESPONSE_TEMPLATE =
"HTTP/1.1 200 OK\r\n" +
"Content-Length: %d\r\n" +
"Content-Type: text/plain\r\n" +
"Connection: close\r\n" +
"\r\n%s";
public static void main(String[] args) throws IOException {
// Open server socket channel
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(PORT));
serverChannel.configureBlocking(false);
// Open selector
Selector selector = Selector.open();
// Register server channel for accept events
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server listening on port " + PORT);
while (true) {
// Wait for events
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = selectedKeys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
iter.remove();
if (key.isAcceptable()) {
handleAccept(key, selector);
}
if (key.isReadable()) {
handleRead(key);
}
if (key.isWritable()) {
handleWrite(key);
}
}
}
}
private static void handleAccept(SelectionKey key, Selector selector) throws IOException {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = serverChannel.accept(); // Accept connection
clientChannel.configureBlocking(false);
System.out.println("Accepted connection from " + clientChannel.getRemoteAddress());
// Register client channel for reading
clientChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
private static void handleRead(SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
int bytesRead = clientChannel.read(buffer);
if (bytesRead == -1) {
// Client closed connection
System.out.println("Client closed connection: " + clientChannel.getRemoteAddress());
clientChannel.close();
key.cancel();
return;
}
// Check if we have received a full HTTP request (simplistic check: look for double CRLF)
String request = new String(buffer.array(), 0, buffer.position(), StandardCharsets.US_ASCII);
if (request.contains("\r\n\r\n")) {
System.out.println("Received request:\n" + request.split("\r\n")[0]); // Print request line
// Prepare response
String response = String.format(RESPONSE_TEMPLATE, RESPONSE_BODY.length(), RESPONSE_BODY);
// Attach response bytes to the key for writing
key.attach(ByteBuffer.wrap(response.getBytes(StandardCharsets.US_ASCII)));
// Change interest to write
key.interestOps(SelectionKey.OP_WRITE);
}
}
private static void handleWrite(SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
clientChannel.write(buffer);
if (!buffer.hasRemaining()) {
// Response fully sent; close connection
System.out.println("Response sent. Closing connection: " + clientChannel.getRemoteAddress());
clientChannel.close();
key.cancel();
}
}
}
ServerSocketChannel & Selector: We create and bind a non-blocking server socket channel, then register it with a selector for accept events.
Event Loop: The server thread loops calling selector.select()
, which blocks until events occur.
Accepting Connections: When isAcceptable()
is true, a new client connection is accepted, configured as non-blocking, and registered for read events with a 1024-byte buffer attached.
Reading Requests: When a channel is readable, we read bytes into the buffer. We do a basic check for the end of the HTTP headers (double CRLF) in the buffer.
Parsing Request: For simplicity, we just print the first request line. Real servers would parse method, path, headers, and body.
Preparing the Response: We create a simple HTTP 200 OK response with plain text body. The response bytes are wrapped into a new ByteBuffer
and attached to the key.
Writing Response: When the channel is writable, we write bytes from the buffer to the client socket channel.
Connection Close: After sending the response fully, the connection is closed and the key canceled.
Single-threaded, multiplexed IO: One thread manages multiple client connections, reacting only when channels are ready, avoiding busy waiting or many threads.
Stateful keys: Each SelectionKey
holds state: a ByteBuffer
for reading and later a buffer for writing response bytes.
InterestOps Switching: We switch interest ops between reading and writing as appropriate. When done writing, the channel is closed.
Efficiency: Non-blocking IO with selectors scales much better than one-thread-per-connection blocking IO, especially under many simultaneous connections.
curl
to access http://localhost:8080/
."Hello from NIO HTTP Server!"
.Basic request parsing only: This server just reads until it sees \r\n\r\n
and does not handle HTTP methods, headers, or request bodies.
No concurrency or thread pool: The single-threaded event loop handles all IO. For CPU-bound tasks, you may want a worker thread pool.
No HTTPS or advanced HTTP features: This is a minimal proof of concept.
Error handling: Production servers need more robust error handling and resource cleanup.
This tutorial demonstrated building a simple HTTP server using Java NIO’s non-blocking IO:
ServerSocketChannel
in non-blocking mode.Selector
to multiplex multiple connections efficiently.With this foundation, you can explore building more advanced servers with richer HTTP support and better scalability.
Input/output (IO) operations are fundamental to many Java applications—reading files, writing logs, communicating over networks, or processing streams. However, IO operations are often a common source of bugs and issues, such as silent failures, resource leaks, or encoding mismatches. Effective logging and debugging practices are essential to identify, diagnose, and fix these problems efficiently.
This section explores common IO issues, how to trace them, and how to implement logging strategies using Java’s built-in logging (java.util.logging
) as well as the popular SLF4J facade, illustrated with IO-specific examples.
A very frequent problem is when IO operations fail silently. For example, failing to close a stream due to swallowed exceptions or ignoring error return codes can cause resource leaks or data corruption.
When reading or writing text files or network data, incorrect or inconsistent character encodings cause garbled text or data loss. For example, reading UTF-8 encoded data using ISO-8859-1 results in corrupted characters.
Reading or writing in incorrect buffer sizes or misunderstanding the non-blocking nature of some channels can cause incomplete data processing or infinite loops.
Accessing the same file or stream from multiple threads without synchronization can lead to unpredictable behavior and hard-to-reproduce bugs.
Common IOExceptions due to missing files or permission denied can halt program execution if not handled or logged properly.
Trace Errors and Exceptions: Logging stack traces and exception messages provides insight into the root cause of failures.
Monitor Resource Usage: Logs can indicate if streams or channels were properly closed.
Detect Encoding Issues Early: Logging the charset used during read/write helps ensure encoding consistency.
Track Data Flow: Debug logs can show what data was read/written, aiding in diagnosing corruption or unexpected input.
java.util.logging
)Java provides a built-in logging API in the java.util.logging
package. Here’s an example demonstrating logging around an IO operation with proper error handling:
import java.io.*;
import java.nio.charset.Charset;
import java.util.logging.*;
public class LoggingIOExample {
private static final Logger logger = Logger.getLogger(LoggingIOExample.class.getName());
public static void readFile(String path, Charset charset) {
logger.info("Starting to read file: " + path + " with charset: " + charset);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(path), charset))) {
String line;
while ((line = reader.readLine()) != null) {
logger.fine("Read line: " + line);
}
} catch (FileNotFoundException e) {
logger.log(Level.SEVERE, "File not found: " + path, e);
} catch (IOException e) {
logger.log(Level.SEVERE, "Error reading file: " + path, e);
}
logger.info("Finished reading file: " + path);
}
public static void main(String[] args) {
// Set log level to show info and above; fine logs won't appear by default
Logger rootLogger = Logger.getLogger("");
rootLogger.setLevel(Level.INFO);
readFile("example.txt", Charset.forName("UTF-8"));
}
}
Logger.getLogger()
to create a logger for the class.info
level).fine
level (debug-level logs; can be enabled in detailed debug mode).logger.log(Level.SEVERE, message, exception)
.By default, java.util.logging
shows logs of level INFO and above. To see DEBUG-level logs (FINE
), configure the logging level programmatically or via a properties file.
SLF4J (Simple Logging Facade for Java) is widely used in enterprise applications for flexible logging abstraction. SLF4J works with popular backends like Logback or Log4j.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.nio.charset.StandardCharsets;
public class Slf4jLoggingIOExample {
private static final Logger logger = LoggerFactory.getLogger(Slf4jLoggingIOExample.class);
public static void writeFile(String path, String content) {
logger.info("Writing to file: {}", path);
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path), StandardCharsets.UTF_8))) {
writer.write(content);
logger.debug("Written content length: {}", content.length());
} catch (IOException e) {
logger.error("Error writing to file: {}", path, e);
}
logger.info("Finished writing to file: {}", path);
}
public static void main(String[] args) {
writeFile("output.txt", "Hello SLF4J IO logging!");
}
}
{}
placeholders) avoids unnecessary string concatenation when logs are disabled.info
, debug
, error
) provide flexible control.Debugging Tips for IO Issues
When reading raw bytes (e.g., from network sockets or files), logging hex dumps or base64 representations of data helps spot corruption.
private static void logBufferContent(ByteBuffer buffer, Logger logger) {
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String hexDump = javax.xml.bind.DatatypeConverter.printHexBinary(bytes);
logger.debug("Buffer content (hex): {}", hexDump);
buffer.position(buffer.position() - bytes.length); // reset position after reading
}
Always log which charset you are using to read or write text, as mismatches often cause hard-to-detect bugs.
IOExceptions often wrap root causes. Use logger.log()
to print full stack traces and investigate nested exceptions.
Log when streams or channels are opened and closed to detect if resources are not properly freed.
In troubleshooting, increase log verbosity to FINE
or DEBUG
for IO classes or packages.
java.util.logging
or SLF4J to log entry/exit points, data content, exceptions, and charset details.By following these guidelines and using logging strategically, you can confidently trace and fix issues in Java IO code—turning black-box problems into understandable and manageable events.
Input/output (IO) is a critical aspect of many Java applications, ranging from file processing to network communication. However, IO operations can become bottlenecks if not handled efficiently. Writing efficient IO code helps reduce latency, improve throughput, and conserve system resources.
This guide covers essential best practices to write performant and robust IO code in Java, with practical examples and tips.
Reading or writing data byte-by-byte or character-by-character is extremely inefficient because each call may result in an expensive system call.
Java provides buffered wrappers:
BufferedInputStream
and BufferedOutputStream
for byte streams.BufferedReader
and BufferedWriter
for character streams.import java.io.*;
public class BufferedFileCopy {
public static void copyFile(File source, File dest) throws IOException {
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dest))) {
byte[] buffer = new byte[8192]; // 8KB buffer
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
}
}
}
Use at least an 8KB buffer size (8192
bytes) for disk IO; smaller buffers may increase overhead, while excessively large buffers waste memory and may cause GC pressure.
Open IO resources (streams, readers, sockets) must be closed promptly to release system resources like file descriptors.
Since Java 7, the try-with-resources statement ensures automatic closing:
try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} // reader is automatically closed here, even if exceptions occur
Avoid manual closing or ignoring exceptions in finally blocks. Try-with-resources is safer, cleaner, and less error-prone.
Buffer size affects latency and throughput:
Experiment with buffer sizes using benchmarks specific to your workload.
java.io
)java.nio
)FileChannel.map()
) for large files with efficient OS paging.Scenario | Recommended API |
---|---|
Simple file read/write | Traditional IO |
High-performance, large files | NIO FileChannel |
Network servers with many clients | NIO with Selectors |
Low concurrency, simple apps | Traditional IO |
IO is often orders of magnitude slower than CPU and memory access. Minimizing the number of IO operations can drastically improve performance.
Java NIO provides direct byte buffers (ByteBuffer.allocateDirect()
) that allocate memory outside the JVM heap and can improve IO performance by avoiding an extra copy between Java heap and OS buffers.
ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
Use direct buffers cautiously—they are more expensive to create and clean up, but beneficial for long-lived buffers in high-performance networking.
If you use traditional IO in UI or event-driven applications, blocking calls can freeze the program.
AsynchronousFileChannel
, AsynchronousSocketChannel
).import java.io.*;
import java.nio.charset.StandardCharsets;
public class EfficientFileReader {
public static void readFile(String path) {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
// Process line
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
BufferedReader
).import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NIONetworkWriteExample {
public static void sendMessage(String host, int port, String message) throws IOException {
try (SocketChannel socketChannel = SocketChannel.open()) {
socketChannel.connect(new InetSocketAddress(host, port));
socketChannel.configureBlocking(true);
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(message.getBytes());
buffer.flip();
while (buffer.hasRemaining()) {
socketChannel.write(buffer);
}
}
}
}
Practice | Why It Matters |
---|---|
Use buffering | Reduces costly system calls, improves throughput |
Use try-with-resources | Prevents resource leaks, simplifies cleanup |
Choose appropriate buffer sizes | Balances memory use and IO efficiency |
Pick IO vs NIO based on need | Simplifies code or enables scalability |
Minimize IO operations | Reduces latency and resource consumption |
Use direct buffers for NIO | Enhances performance in networking and large file IO |
Avoid blocking calls on main threads | Keeps UI and event loops responsive |
By following these best practices, you can write IO code in Java that is not only functionally correct but also efficient and scalable—helping your applications run smoothly in real-world environments.