Java has two I/O layers: the classic java.io streams (bytes and characters, decorated with buffering) and the newer java.nio.file API built around Path and Files. This guide covers writing files, how the stream hierarchies fit together, and the NIO.2 features. For reading test data, see How to Read a File in Java.
File I/O — java.io & java.nio
Reading & Writing Files
import java.io.*;
import java.nio.file.*;
// ── CLASSIC java.io ──
// Write to file
try (FileWriter fw = new FileWriter("output.txt");
BufferedWriter bw = new BufferedWriter(fw)) {
bw.write("Hello World!");
bw.newLine();
bw.write("Line 2");
} // auto-closed
// Read from file
try (FileReader fr = new FileReader("output.txt");
BufferedReader br = new BufferedReader(fr)) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
// ── MODERN java.nio (Java 7+) ──
Path path = Paths.get("output.txt");
// Write all text
Files.writeString(path, "Hello NIO!"); // Java 11+
Files.write(path, List.of("line1", "line2")); // write lines
// Read all
String content = Files.readString(path); // Java 11+
List<String> lines = Files.readAllLines(path);
byte[] bytes = Files.readAllBytes(path);
// Stream lines (efficient for large files)
try (Stream<String> stream = Files.lines(path)) {
stream.filter(l -> l.contains("Java"))
.forEach(System.out::println);
}
// File operations
Files.exists(path);
Files.createDirectory(Paths.get("myDir"));
Files.copy(src, dest, StandardCopyOption.REPLACE_EXISTING);
Files.move(src, dest);
Files.delete(path);
Files.deleteIfExists(path);
// Walk directory tree
Files.walk(Paths.get("."))
.filter(p -> p.toString().endsWith(".java"))
.forEach(System.out::println);
InputStream / OutputStream Hierarchy
// BYTE STREAMS (binary data)
// InputStream hierarchy:
// InputStream
// ├── FileInputStream
// ├── ByteArrayInputStream
// ├── PipedInputStream
// └── FilterInputStream
// ├── BufferedInputStream
// ├── DataInputStream
// └── CheckedInputStream
// ── FileInputStream / FileOutputStream ──
// Read bytes
try (FileInputStream fis = new FileInputStream("input.bin")) {
int byteVal;
while ((byteVal = fis.read()) != -1) { // -1 = end of stream
System.out.print((char) byteVal);
}
// OR read chunk:
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
process(buffer, 0, bytesRead);
}
}
// Write bytes
try (FileOutputStream fos = new FileOutputStream("output.bin")) {
fos.write(65); // write single byte
fos.write(new byte[]{72,101,108,108,111}); // write bytes
}
// Append mode:
new FileOutputStream("file.bin", true); // second arg = append
// ── BufferedInputStream / BufferedOutputStream ──
// Wraps stream with internal buffer — reduces system calls
try (BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("file.bin"), 8192)) { // 8KB buffer
// ... read efficiently
}
// ── ByteArrayInputStream / ByteArrayOutputStream ──
// Work with byte arrays in memory (no file needed)
byte[] data = {72,101,108,108,111}; // "Hello" in bytes
ByteArrayInputStream bais = new ByteArrayInputStream(data);
int b = bais.read(); // 72 ('H')
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(72); baos.write(101); baos.write(108);
byte[] result = baos.toByteArray();
String str = baos.toString("UTF-8");
// ── DataInputStream / DataOutputStream ──
// Write/read primitive types as bytes
try (DataOutputStream dos = new DataOutputStream(
new FileOutputStream("data.bin"))) {
dos.writeInt(42);
dos.writeDouble(3.14);
dos.writeBoolean(true);
dos.writeUTF("Hello"); // length-prefixed UTF-8
}
try (DataInputStream dis = new DataInputStream(
new FileInputStream("data.bin"))) {
int i = dis.readInt(); // 42
double d = dis.readDouble(); // 3.14
boolean b2 = dis.readBoolean(); // true
String s = dis.readUTF(); // "Hello"
}
// ── PipedInputStream / PipedOutputStream ──
// Connect two threads: one writes, other reads
PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pis = new PipedInputStream(pos);
new Thread(() -> {
try { pos.write("Data from producer".getBytes()); pos.close(); }
catch(IOException e) {}
}).start();
new Thread(() -> {
try { System.out.println(new String(pis.readAllBytes())); pis.close(); }
catch(IOException e) {}
}).start();
Reader/Writer Hierarchy — Character Streams
// CHARACTER STREAMS (text data, handles encoding)
// Reader hierarchy:
// Reader
// ├── FileReader
// ├── StringReader
// ├── CharArrayReader
// ├── PipedReader
// ├── InputStreamReader (bridge: bytes→chars)
// └── BufferedReader
// └── LineNumberReader
// ── BufferedReader (most common for text) ──
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
// Read line by line
String line;
while ((line = br.readLine()) != null) { // null = end of file
System.out.println(line);
}
// OR stream (Java 8+):
br.lines().forEach(System.out::println);
br.lines().filter(l -> l.contains("TODO")).count();
}
// ── BufferedWriter / PrintWriter ──
try (BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"))) {
bw.write("First line");
bw.newLine(); // platform-appropriate line separator
bw.write("Second line");
bw.flush(); // ensure data written
}
// PrintWriter: printf-style writing
try (PrintWriter pw = new PrintWriter(new FileWriter("out.txt"))) {
pw.println("Hello World"); // with newline
pw.printf("Value: %d%n", 42);
pw.format("Name: %-10s Age: %d%n", "Alice", 30);
}
// ── InputStreamReader: specify encoding ──
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream("file.txt"), "UTF-8"))) {
br.lines().forEach(System.out::println);
}
// Files.newBufferedReader(path, charset) is simpler
// ── StringReader / StringWriter ──
StringReader sr = new StringReader("Hello World");
int ch; StringBuilder sb = new StringBuilder();
while ((ch = sr.read()) != -1) sb.append((char)ch);
StringWriter sw = new StringWriter();
sw.write("Hello "); sw.write("World");
String text = sw.toString(); // "Hello World"
// ── LineNumberReader ──
try (LineNumberReader lnr = new LineNumberReader(new FileReader("file.txt"))) {
String line;
while ((line = lnr.readLine()) != null) {
System.out.printf("Line %d: %s%n", lnr.getLineNumber(), line);
}
}
NIO.2 — WatchService & AsynchronousFileChannel
import java.nio.file.*;
import java.nio.channels.*;
// ── WATCH SERVICE: monitor directory for changes ──
WatchService watcher = FileSystems.getDefault().newWatchService();
Path dir = Paths.get("/home/user/documents");
dir.register(watcher,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);
System.out.println("Watching: " + dir);
while (true) {
WatchKey key = watcher.take(); // blocks until event
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
Path filename = (Path) event.context();
System.out.println(kind + ": " + filename);
}
boolean valid = key.reset(); // reset to receive more events
if (!valid) break; // directory no longer accessible
}
// ── ASYNC FILE CHANNEL ──
Path path = Paths.get("large-file.bin");
AsynchronousFileChannel afc = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;
// Read with CompletionHandler
afc.read(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer bytesRead, ByteBuffer buf) {
buf.flip();
System.out.println("Read " + bytesRead + " bytes");
buf.clear();
}
@Override
public void failed(Throwable exc, ByteBuffer buf) {
System.err.println("Read failed: " + exc.getMessage());
}
});
// Read with Future
Future<Integer> future = afc.read(buffer, 0);
int bytesRead = future.get(); // wait for completion
// ── FILES UTILITY — COMPLETE ──
Path p = Paths.get("file.txt");
Files.exists(p) // check existence
Files.notExists(p) // check non-existence
Files.isRegularFile(p) // is it a file?
Files.isDirectory(p) // is it a directory?
Files.isReadable(p) // has read permission?
Files.isWritable(p) // has write permission?
Files.isExecutable(p) // has execute permission?
Files.size(p) // size in bytes
Files.getLastModifiedTime(p) // last modification time
Files.getAttribute(p, "basic:creationTime")
Files.probeContentType(p) // "text/plain", "image/jpeg" etc.
Files.createFile(p) // create new file
Files.createDirectory(Paths.get("dir"))
Files.createDirectories(Paths.get("a/b/c")) // create all parents too
Files.createTempFile("prefix", ".tmp")
Files.createTempDirectory("tmpDir")
Files.move(src, dest, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE)
Files.copy(src, dest, StandardCopyOption.COPY_ATTRIBUTES)
Files.delete(p) // throws if not exists
Files.deleteIfExists(p) // no exception if not exists
// Walk file tree
Files.walk(Paths.get("."), 3) // max depth 3
.filter(Files::isRegularFile)
.filter(f -> f.toString().endsWith(".java"))
.forEach(System.out::println);
// Walk with FileVisitor (more control)
Files.walkFileTree(Paths.get("."), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
System.out.println(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
return FileVisitResult.SKIP_SUBTREE;
}
});
FAQs
What is the difference between byte streams and character streams?
Byte streams (InputStream, OutputStream) move raw bytes, for images or any binary data; character streams (Reader, Writer) handle text and convert bytes using a charset.
Should I use java.io or java.nio?
For files, prefer java.nio.file.Files and Path: simpler methods, better errors and explicit charsets. java.io streams are still used for wrapping sockets and other streams.
How do you write to a file in Java?
Files.writeString(path, text) (Java 11+) for a whole string, Files.write(path, lines) for lines, or a BufferedWriter from Files.newBufferedWriter for large output.