1. Overview

In this tutorial, we’ll explore different ways of creating a symbolic link in Java using the NIO.2 API, and explore the differences between hard and soft file links.

First, let’s define what file links are and what is their expected behavior. A file link is a pointer that transparently references a file stored in the file system.

A common misunderstanding is thinking that a file link is a shortcut, so let’s check their behavior:

  • A shortcut is a regular file that references a target file
  • Soft/Symbolic link is a file pointer that behaves as the file that is linking to – if the target file gets deleted then the link is unusable
  • A hard link is a file pointer that mirrors the file that it’s linking to, so it’s basically like a clone. If the target file gets deleted, the link file is still valid

Most operating systems (Linux, Windows, Mac) already support soft/hard file links, so it shouldn’t be a problem to work over them using the NIO API.

First, we have to create a target file to link to, so let’s sequence some data into a file:

public Path createTextFile() throws IOException {        
    byte[] content = IntStream.range(0, 10000)
      .mapToObj(i -> i + System.lineSeparator())
      .reduce("", String::concat)
      .getBytes(StandardCharsets.UTF_8);
    Path filePath = Paths.get("", "target_link.txt");
    Files.write(filePath, content, CREATE, TRUNCATE_EXISTING);
    return filePath;        
}

Let’s create a symbolic link to an existing file, ensuring that the created file is a symbolic link:

public void createSymbolicLink() throws IOException {
    Path target = createTextFile();
    Path link = Paths.get(".","symbolic_link.txt");
    if (Files.exists(link)) {
        Files.delete(link);
    }
    Files.createSymbolicLink(link, target);
}

Next, let’s take a look at a hard link creation:

public void createHardLink() throws IOException {
    Path target = createTextFile();
    Path link = Paths.get(".", "hard_link.txt");
    if (Files.exists(link)) {
        Files.delete(link);
    }
    Files.createLink(link, target);
}

By listing the files with their differences, we can see that the soft/symbolic link file size is small, while the hard link is using the same space as the linked file:

 48K    target_link.txt
 48K    hard_link.txt
4.0K    symbolic_link.txt

To clearly understand what are the possible exceptions that can be thrown, let’s see the checked exceptions on the operations:

  • UnsupportedOperationException – when the JVM doesn’t support file links in a specific system
  • FileAlreadyExistsException – when the link file already exists, the override is not supported by default
  • IOException – when an IO error occurs, e.g. invalid file path
  • SecurityException – when the link file can’t be created or the target file can’t be accessed because of limited file permissions

Now, if we have a given file system with existing file links, it’s possible to identify them and show their target files:

public void printLinkFiles(Path path) throws IOException {
    try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
        for (Path file : stream) {
            if (Files.isDirectory(file)) {
                printLinkFiles(file);
            } else if (Files.isSymbolicLink(file)) {
                System.out.format("File link '%s' with target '%s' %n", 
                  file, Files.readSymbolicLink(file));
            }
        }
    }
}

If we execute it in our current path:

printLinkFiles(Paths.get("."));

We would get the output:

File link 'symbolic_link.txt' with target 'target_link.txt'

Note that hard link files aren’t simply identifiable with NIO’s APIlow-level operations are required to work over that kind of files.

5. Conclusion

This article describes the different type of file links, their difference with shortcuts, and how to create and operate over them using a pure Java API that works over the mainstream file systems on the market.

The implementation of these examples and code snippets can be found over on GitHub.