Listing directory contents, moving, copying, deleting files - feature image

Java files, part 4: Listing directory contents / Moving, copying, deleting files

by Sven Woltmann – January 29, 2020

Previous articles in this series have covered reading files with Java, writing files, and constructing directory and file paths with the File and Path classes. This fourth part describes the most important directory and file operations. It answers the following questions:

  • How to list all files in a directory?
  • How to search for files matching specific criteria within a directory tree?
  • How to find the current directory?
  • How to find the user’s home directory?
  • How to find the temporary directory, and how to create a temporary file?
  • How do I move files with Java?
  • How do I rename a file?
  • How do I copy a file?
  • How to delete a file?
  • How to create a symbolic link with Java?

The article answers all questions using the NIO.2 File API, introduced in Java 7 with JSR 203.

Directory operations

For the following directory operations, you need a Path-object representing the directory. You can construct this object using the static method Path.of() (or, before Java 11, using Paths.get()).

A comprehensive tutorial on constructing directory paths with Path, Paths, and File can be found in the third part of this series.

How to list directory contents with Files.list()

The easiest way to list the complete contents of a directory is the Files.list() method. It returns a Stream of Path objects, which we simply write to System.out in the following example:

Path currentDir = Path.of(System.getProperty("user.home"));
Files.list(currentDir).forEach(System.out::println);

How to search a directory recursively with Files.list()

Let’s move on to a more complex case. In the following example, we want to output all regular files located in the home directory or a subdirectory of any depth thereof and whose name starts with “settings”.

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

public class FindFilesRecursivelyExample {
  public static void main(String[] args) throws IOException {
    Path currentDir = Path.of(System.getProperty("user.home"));
    findFileRecursively(currentDir, "settings");
  }

  private static void findFileRecursively(
        Path currentDir, String fileNamePrefix) throws IOException {
    Files.list(currentDir).forEach(child -> {
      if (Files.isRegularFile(child)
            && child.getFileName().toString().startsWith(fileNamePrefix)) {
        System.out.println(child);
      }

      if (Files.isDirectory(child) ) {
        try {
          findFileRecursively(child, fileNamePrefix);
        } catch (AccessDeniedException e) {
          System.out.println("Access denied: " + child);
        } catch (IOException e) {
          throw new UncheckedIOException(e);
        }
      }
    });
  }
}

You can use the methods Files.isRegularFile() and Files.isDirectory() to check if a file is a regular file or directory. Another file type is the symbolic link – you can recognize it with Files.isSymbolicLink(). It is also possible that all three methods return false, in which case the file is of type “other” (what exactly this could be is unspecified).

In the example above, we have to catch IOException inside the lambda and wrap it with an UncheckedIOException because the forEach consumer of the stream must not throw a checked exception.

How to search a directory recursively with Files.walk()

You can write the previous example much shorter and more elegant – using Files.walk():

private static void findFileWithWalk(Path currentDir, String fileNamePrefix)
      throws IOException {
  Files.walk(currentDir).forEach(child -> {
    if (Files.isRegularFile(child)
          && child.getFileName().toString().startsWith(fileNamePrefix)) {
      System.out.println(child);
    }
  });
}

However, this variant has the disadvantage that an AccessDeniedException cannot be caught individually as before. If such an exception occurs here, the entire Files.walk() method terminates. If this is acceptable for your application, this way is more beautiful than the previous one.

How to search a directory recursively with Files.walkFileTree()

Another variant is Files.walkFileTree(). This method implements the visitor pattern. It sends each file within the directory structure to a FileVisitor, which you pass to the method. In the following example, we use the SimpleFileVisitor class, which implements all methods of FileVisitor. We only override the visitFile() method:

private static void findFileWithWalkFileTree(
      Path currentDir, String fileNamePrefix) throws IOException {
  Files.walkFileTree(currentDir, new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
          throws IOException {
      if (Files.isRegularFile(file)
            && file.getFileName().toString().startsWith(fileNamePrefix)) {
        System.out.println(file);
      }
      return FileVisitResult.CONTINUE;
    }
  });
}

The method’s return value, FileVisitResult.CONTINUE, indicates that walkFileTree() should continue to traverse the directory tree. Other return values would be:

  • TERMINATE – terminates the walkFileTree() method.
  • SKIP_SIBLINGS – skips all other files of the current directory.
  • SKIP_SUBDIR – skips the current directory – this return value cannot be returned by visitFile(), but by FileVisitor.preVisitDirectory().

The walkFileTree() variant, too, would terminate processing in the case of an AccessDeniedException. However, there is a way to prevent this. To do so, you also need to overwrite the method FileVisitor.visitFileFailed():

private static void findFileWithWalkFileTree(
      Path currentDir, String fileNamePrefix) throws IOException {
  Files.walkFileTree(currentDir, new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
          throws IOException {
      if (Files.isRegularFile(file)
            && file.getFileName().toString().startsWith(fileNamePrefix)) {
        System.out.println(file);
      }
      return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) 
          throws IOException {
      if (exc instanceof AccessDeniedException) {
        System.out.println("Access denied: " + file);
        return FileVisitResult.CONTINUE;
      } else {
        return super.visitFileFailed(file, exc);
      }
    }
  });
}

How to search a directory with Files.find()

An alternative approach is the Files.find() method. It expects a “matcher” as the third parameter: this is a function that has a Path and BasicFileAttributes as input parameters and returns a boolean indicating whether the corresponding file should be included in the result or not.

private static void findFileWithFind(Path currentDir, String fileNamePrefix)
      throws IOException {
  Files.find(currentDir, Integer.MAX_VALUE,
        (path, attributes) -> Files.isRegularFile(path)
              && path.getFileName().toString().startsWith(fileNamePrefix))
        .forEach(System.out::println);
}

Again, an AccessDeniedException would end the entire search prematurely. I don’t know of any way to circumvent this with Files.find(). If you expect subdirectories with denied access, you should either use Files.walkFileTree() and overwrite the visitFileFailed() method to catch the exception. Alternatively, use Files.list() and implement the recursion yourself.

Accessing specific directories

Attention: If you are using a version older than Java 11, you must replace Path.of() with Paths.get() in the following code examples.

Accessing the current directory

The current directory can be found via the system property “user.dir”:

Path currentDir = Path.of(System.getProperty("user.dir"));

Accessing the user’s home directory

You can find the home directory of the current user via the system property “user.home”:

Path homeDir = Path.of(System.getProperty("user.home"));

Accessing the temporary directory

And you can find the temporary directory via the system property “java.io.tmpdir”:

Path tempDir = Path.of(System.getProperty("java.io.tmpdir"));

If you want to create a temporary file in the temporary directory, you do not need to access the temporary directory first. There is a shortcut for this. You will find it at the beginning of the following chapter, “File operations”.

File operations

How to create a temporary file

After the last chapter concluded with accessing the temporary directory, this one starts with a shortcut to creating temporary files:

Path tempFile = Files.createTempFile("happycoders-", ".tmp");

The two parameters are a prefix and a suffix. The createTempFile() method will insert a random number between them. When I run the method repeatedly on my Windows system and print the variable tempFile to the console, I get the following output:

tempFile = C:\Users\svenw\AppData\Local\Temp\happycoders-7164892815754554616.tmp
tempFile = C:\Users\svenw\AppData\Local\Temp\happycoders-3557939636108137420.tmp
tempFile = C:\Users\svenw\AppData\Local\Temp\happycoders-16515581992479122220.tmp
tempFile = C:\Users\svenw\AppData\Local\Temp\happycoders-4078166990204004103.tmp

On Linux it looks like this:

tempFile = /tmp/happycoders-6859515894563322081.tmp
tempFile = /tmp/happycoders-3688163816397144832.tmp
tempFile = /tmp/happycoders-2576679508175526427.tmp
tempFile = /tmp/happycoders-8074586277964353976.tmp

It is essential to know that createTempFile() does not only create the respective Path object but actually creates an empty file.

How to move a file in Java

To move a file, use the method Files.move(). The following example creates a temporary file and moves it to the home directory of the logged-on user:

Path tempFile = Files.createTempFile("happycoders-", ".tmp");
Path targetDir = Path.of(System.getProperty("user.home"));
Path target = targetDir.resolve(tempFile.getFileName());
Files.move(tempFile, target);

The second parameter of the move() method must represent the target file, not the target directory! If you invoked Files.move(tempFile, targetDir) here, you would get a FileAlreadyExistsException. Therefore, in the example, we use the resolve() method to concatenate the targetDir with the name of the file to be copied.

How to move a directory including all subdirectories

You can move a directory just like a file. In the following example, we create two temporary directories and one file in the first directory. We then move the first directory into the second:

Path tempDir1 = Files.createTempDirectory("happycoders-");
Path tempDir2 = Files.createTempDirectory("happycoders-");
Path tempFile = Files.createTempFile(tempDir1, "happycoders-", ".tmp");
Path target = tempDir2.resolve(tempDir1.getFileName());
Files.move(tempDir1, target);

How to rename file with Java

After all, renaming a file (or a directory) is a special case of moving, with the destination directory being the same as the source directory and only the file name changing. In the following example, we rename a temporary file to “happycoders.tmp”:

Path tempFile = Files.createTempFile("happycoders-", ".tmp");
Path target = tempFile.resolveSibling("happycoders.tmp");
Files.move(tempFile, target);

Invoking tempFile.resolveSibling("happycoders.tmp") is a shortcut for tempFile.getParent().resolve("happycoders.tmp"): The directory is extracted from the source file and concatenated with the new file name.

How to copy a file in Java

Copying a file is similar to renaming it. The following example copies a temporary file to the home directory:

Path tempFile = Files.createTempFile("happycoders-", ".tmp");
Path targetDir = Path.of(System.getProperty("user.home"));
Path target = targetDir.resolve(tempFile.getFileName());
Files.copy(tempFile, target);

This method has a significant advantage over proprietary implementations with FileInputStream and FileOutputStream, as they were necessary before Java 7 and the NIO.2 File API: Files.copy() delegates the call to operating system-specific – and thus optimized – implementations.

How to delete a file in Java

You can delete a file (or a directory) with Files.delete():

Path tempFile = Files.createTempFile("happycoders-", ".tmp");
Files.delete(tempFile);

The directory on which you invoke Files.delete() must be empty. Otherwise, the method will throw a DirectoryNotEmptyException. You can try this with the following code:

Path tempDir = Files.createTempDirectory("happycoders-");
Path tempFile = Files.createTempFile(tempDir, "happycoders-", ".tmp");
Files.delete(tempDir);

First, a temporary directory is created, then a temporary file in it. Then an attempt is made to delete the (non-empty) directory.

How to create a symbolic link to a file

You can create a symbolic link with the method Files.createSymbolicLink(). Attention: You have to specify target and source in reverse order as with all previous methods: first, the link path, then the path of the file to be linked. The following example creates a temporary file and then sets a symbolic link to the created file from the home directory.

Path tempFile = Files.createTempFile("happycoders-", ".tmp");
Path linkDir = Paths.get(System.getProperty("user.home"));
Path link = linkDir.resolve(tempFile.getFileName());
Files.createSymbolicLink(link, tempFile);

This example works on Linux without restrictions. On Windows, you need administrative rights to create symbolic links. If these are missing, the following exception is thrown: FileSystemException: [link]: A required privilege is not held by the client.

Summary and outlook

This fourth article in the series about files in Java introduced the most important directory and file operations.

In the next part, I will show you how to write and read structured data with DataOutputStream and DataInputStream.

We then move on to the following advanced topics:

  • NIO channels and buffers introduced in Java 1.4, to speed up working with large files
  • Memory-mapped I/O for convenient and blazing-fast file access without streams
  • File locking, to access the same files in parallel – i.e., from several threads or processes – without conflict

As always, I appreciate it if you share the article or your feedback via the comment function. Would you like to be informed when the next part is published? Then please subscribe to my mailing list using the following form.

You might also like the following articles
Leave a Comment

Your email address will not be published. Required fields are marked *

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}