How do I read a file line by line using Java NIO?

The java.nio.file.Files.lines() method is a Java NIO method used to read the contents of a file line by line. The code snippet will read the file from the specified filePath and print each line to the console. The Files.lines() method returns a Stream of strings, and we use Stream’s forEach method to print each line.

Note that we are using a try-with-resources statement which will automatically close the stream after we are done with it, it’s a good practice to always close streams to free-up system resources.

Here’s a basic example of how you can use it.

package org.kodejava.io;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.IOException;
import java.util.stream.Stream;

public class ReadFile {
    public static void main(String[] args) {
        // replace with your file path
        String filePath = "/Users/wayan/lipsum.txt";

        // read file into stream, try-with-resources
        try (Stream<String> stream = Files.lines(Paths.get(filePath))) {

            stream.map(String::trim)
                    .forEach(System.out::println);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

The Files.lines() method, along with other Java I/O and NIO methods, provide several important benefits and features:

  1. Memory Efficiency: This method reads the file line by line lazily, which means it doesn’t load the entire content of the file into memory. This is particularly useful when dealing with large files that could potentially exhaust system memory.
  2. Stream API Integration: The method returns a Stream<String>, it can naturally integrate with Java Stream API. This allows you to take advantage of powerful functions provided by Stream API such as filtering, mapping, reduction, etc., to process the file data effectively.
  3. Readability: Using Files.lines() with a try-with-resources construct results in more compact and readable code compared to older methods such as BufferedReader. The try-with-resources statement ensures that each resource is closed at the end of the statement, which can simplify cleanup code and avoid resource leaks.
  4. Exceptions Handling: I/O operations can generally throw IOException which are checked Exceptions in Java. Using Files.lines() within a try-with-resources statement ensures that any underlying resources are closed properly, even in the event of an Exception.
  5. Parallel Processing: Since Files.lines() method returns a Stream<String>, you can convert this stream into a parallel stream if you want to process the file with multithreading.

Remember, like with many programming choices, whether to use Files.lines() or another method depends on the specific needs and constraints of your project.

How do I read last n characters from a file?

In the following post you will learn how to read last n characters from a file. The JDK 7 introduces a new SeekableByteChannel interface which allows its implementation to change the position and the size of the byte channel. One of its implementation is the FileChannel class (java.nio.channels.FileChannel).

The FileChannel class make it possible to get hold the current position of where we are going to read from or write to a file. The code snippet below shows you how you can read the last 1000 characters from a log file.

package org.kodejava.io;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class FileReadLastNCharacters {
    public static void main(String[] args) {
        // Defines the path to the log file and creates a ByteBuffer.
        Path logPath = Paths.get("C:/tools/apache-tomcat-10.0.11/logs/catalina.out");
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        try {
            // Creates FileChannel and open the file channel for read access.
            FileChannel channel = FileChannel.open(logPath, StandardOpenOption.READ);

            // Read a sequence of bytes from the channel into the buffer starting
            // at given file position, which is the channel size - 1000. Because
            // we are going to read the last 1000 characters from the file.
            channel.read(buffer, channel.size() - 1000);
            System.out.println("Characters = " + new String(buffer.array()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

The steps in the code snippet above are:

  • Get the path to the log file.
  • Create a ByteBuffer, a buffer where the read bytes to be transferred.
  • Using the FileChannel.open() method we open a file to be read and return a FileChannel object.
  • The read() method of the FileChannel reads a sequence of bytes from the channel and transfer them to the given buffer starting and the position defined by channel.size() - 1000. This method returns the number of bytes read, possible zero, or -1 if the given position is greater than or equal to the file’s current size.
  • Print out the buffered string.

How to monitor file or directory changes?

package org.kodejava.io;

import java.io.IOException;
import java.nio.file.*;

import static java.nio.file.StandardWatchEventKinds.*;

public class FileWatchDemo {
    public static void main(String[] args) {
        try {
            // Creates a instance of WatchService.
            WatchService watcher = FileSystems.getDefault().newWatchService();

            // Registers the logDir below with a watch service.
            Path logDir = Paths.get("F:/Temp/");
            logDir.register(watcher, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);

            // Monitor the logDir at listen for change notification.
            while (true) {
                WatchKey key = watcher.take();
                for (WatchEvent<?> event : key.pollEvents()) {
                    WatchEvent.Kind<?> kind = event.kind();

                    if (ENTRY_CREATE.equals(kind)) {
                        System.out.println("Entry was created on log dir.");
                    } else if (ENTRY_MODIFY.equals(kind)) {
                        System.out.println("Entry was modified on log dir.");
                    } else if (ENTRY_DELETE.equals(kind)) {
                        System.out.println("Entry was deleted from log dir.");
                    }
                }
                key.reset();
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

To get the created, modified or deleted file you can see the following example: How to get the file name when using WatchService?.

How to get the file name when using WatchService?

package org.kodejava.io;

import java.io.IOException;
import java.nio.file.*;

import static java.nio.file.StandardWatchEventKinds.*;

public class WatchServiceGetFilename {
    public static void main(String[] args) {
        try {
            // Create a WatchService and register the logDir path with the
            // WatchService for ENTRY_CREATE.
            WatchService watcher = FileSystems.getDefault().newWatchService();
            Path logDir = Paths.get("F:/Temp");
            logDir.register(watcher, ENTRY_CREATE);

            while (true) {
                WatchKey key;
                try {
                    key = watcher.take();
                } catch (InterruptedException e) {
                    return;
                }

                for (WatchEvent<?> event : key.pollEvents()) {
                    if (event.kind() == ENTRY_CREATE) {
                        // Get the name of created file.
                        WatchEvent<Path> ev = cast(event);
                        Path filename = ev.context();

                        System.out.printf("A new file %s was created.%n",
                                filename.getFileName());
                    }
                }
                key.reset();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @SuppressWarnings("unchecked")
    private static <T> WatchEvent<T> cast(WatchEvent<?> event) {
        return (WatchEvent<T>) event;
    }
}

How to write file using Files.newBufferedWriter?

To open a file for writing in JDK 7 you can use the Files.newBufferedWriter() method. This method takes three arguments. We need to pass the Path, the Charset and a varargs of OpenOption.

For example, in the snippet below we pass the path of our log file, we use the StandardCharsets.UTF_8 charset, and we use the StandardOpenOption.WRITE to open a file for writing. If you want to open a file and append its contents instead of rewriting it you can use the StandardOpenOption.APPEND.

package org.kodejava.io;

import java.io.BufferedWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class FilesNewBufferedWriter {
    public static void main(String[] args) throws Exception {
        Path logFile = Paths.get("app.log");
        if (Files.notExists(logFile)) {
            Files.createFile(logFile);
        }

        try (BufferedWriter writer =
                     Files.newBufferedWriter(logFile, StandardCharsets.UTF_8,
                             StandardOpenOption.WRITE)) {

            for (int i = 0; i < 10; i++) {
                writer.write(String.format("Message %s%n", i));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Because we use the StandardOpenOption.WRITE we have to make sure that the file to be written is exists. If the file is not available we will get error like java.nio.file.NoSuchFileException.