One of the most important features in the NIO.2 library is the introduction of mechanism to work with links directly from your Java code (this was not possible to do without the use of native code in previous IO libraries). Most of the advanced users are already familiar with concept of hard links and symbolic links. Before we start to talk about links lets review some basic characteristics of both types:
- Hard link or simply link is a directory entry that links file with name in given file system. Single file can be linked using multiple hard links. Hard link must always have an existing target and this target can only be a file. User does not lose access to a target file by removing a hard link. Hard link provides linked file even after the target file was removed. Hard links are always bound to a single file system.
- Symbolic link or soft link is a specific file containing relative or absolute path to a target file. Single file can be linked using multiple symbolic links. Symbolic links do not require an existing target and their target might be file or directory. User does not lose access to a target file by removing a symbolic link. Symbolic link is not influenced by removing of the target file, since its target is not required to exist. These kind of links are called dead links or broken links. Symbolic links are not bound to a single file system.
Before we start with this topic please note that support for links is highly platform and file system provider dependent so it is always good idea to consult documentation before any link related programming.
Hard links
There is pretty convenient way to create both hard and symbolic links and it involves work with java.nio.file.Files
class. Use the method createLink
to create a hard link. Following example shows how easy it is to create a hard link using Files
class. I used exists
method to check whether any link was created.
Path target = Paths.get("c:/a.txt"); Path link = Paths.get("c:/links/b.txt"); // creates hard link Files.createLink(link, target); // returns true Files.exists(link);
It is always good to keep in mind that hard links require target file to exist. If you don’t check the existence of your target file you might end up with a checked exception NoSuchFileException
just like method body in following example.
Path target = Paths.get("c:/b.txt"); Path link = Paths.get("c:/links/b.txt"); // returns false Files.exists(target); // throws java.nio.file.NoSuchFileException: c:\links\b.txt -> c:\b.txt Files.createLink(link, target);
Another example of code that results in checked exception is any attempt to create a hard link pointing to a directory (hard links can be created only for files as mentioned earlier). Following code demonstrates such a behavior.
Path target = Paths.get("c:/temp"); Path link = Paths.get("c:/links/tempLink"); // throws AccessDeniedException: c:\links\tempLink -> c:\temp Files.createLink(link, target);
To avoid any confusion that could be caused be the exception class name it is good to check out Javadoc for AccessDeniedException
class:
Checked exception thrown when a file system operation is denied, typically due to a file permission or other access check. This exception is not related to the
AccessControlException
orSecurityException
thrown by access controllers or security managers when access to a file is denied.
Symbolic links
Symbolic link creation is very similar to the previous example and requires the use of method createSymbolicLink
from Files
class. Since symbolic link allows programmer to link files and directories as well as non-existing file system locations following example successfully creates three symbolic links.
Path fileTarget = Paths.get("c:/a.txt"); Path directoryTarget = Paths.get("c:/temp"); Path nonExistingTarget = Paths.get("c:/b.txt"); Path directoryLink = Paths.get("c:/links/linkTemp"); Path fileLink1 = Paths.get("c:/links/c.txt"); Path fileLink2 = Paths.get("c:/links/d.txt"); // returns true Files.exists(fileTarget); // returns true Files.exists(directoryTarget); // returns false Files.exists(nonExistingTarget); // creates three symbolic links Files.createSymbolicLink(fileLink1, fileTarget); Files.createSymbolicLink(directoryLink, directoryTarget); Files.createSymbolicLink(fileLink2, nonExistingTarget);
Please note that both hard and symbolic links require link path to be pointing to a non-existing location on file system otherwise you receive FileAlreadyExistsException
.
One specific trait of symbolic links that differentiates them from hard links is the fact that they are in fact files. This means that they have their own file system metadata (file attributes). One important thing to bear in mind is the fact the creation of symbolic link and population of its file attributes is an atomic operation in NIO.2, which was not possible to achieve without native code until now. Having said that I would love to show you how to do this. But since one of core duties of FileSystemProvider
is to initialize file attributes, we need FileSystemProvider
‘s support to do this. However neither UnixFileSystemProvider
nor WindowsFileSystemProvider
does not support symbolic links to be initialized with file attributes just yet. Based on what I was able to find in their sources we can expect this functionality in upcoming release of Java 8. It is possible to achieve this functionality, but it requires working with other (custom) file system / file system provider that supports this feature.
Exploring target of link
Now that we know how to create both types of links it is a good idea to be able to explore the target of these links. Since hard links must always have an existing target and the target must be file there is no need to explore this target further. However symbolic links are more flexible than that and it is good to know how to explore their target. To do this we will turn to Files
class and use its readSymbolicLink
method. This method has one parameter – path of a link. If we manage to pass it a path that is not a link we will end up with NotLinkException
. Whole situation is described in following snippet.
Path target = Paths.get("c:/temp"); Path link = Paths.get("c:/links/tempLink"); Path path = Paths.get("c:/a.txt"); // creates hard link Files.createSymbolicLink(link, target); // returns c:\temp Files.readSymbolicLink(link); // throws java.nio.file.NotLinkException: The file or directory is not a reparse point. Files.readSymbolicLink(path);
Link detection
To prevent the NotLinkException
from previous example we need a way to detect whether certain path is a link or not. Files
class provides such a method and it name is isSymbolicLink
. Following example demonstrates the use of this method – no surprises there.
Path target = Paths.get("c:/a.txt"); Path hardLink = Paths.get("c:/links/harLink.txt"); Path symbolicLink = Paths.get("c:/links/symbolicLink.txt"); // creates test links Files.createLink(hardLink, target); Files.createSymbolicLink(symbolicLink, target); // returns false Files.isSymbolicLink(target); // returns false Files.isSymbolicLink(hardLink); // returns true Files.isSymbolicLink(symbolicLink);
File system operations
Last area of links that I haven’t covered so far is the way library classes handle links when they stumble upon them while performing their duties. These ‘link aware’ methods often include working with file attributes or checking whether a path is file or a directory and validating its existence. To guide these methods NIO.2 provides java.nio.file.LinkOption
enum. This enum (as stated in the Javadoc) defines the options as to how symbolic links are handled. There is only one value available so far and it is NOFOLLOW_LINKS
which makes these methods treat links as plain old paths.
Lets consider an example – a method I mentioned in File Attributes post for reading file metadata, readAttributes
. This method is nice example of the need to be able to differentiate whether the algorithm should work with the target of the symbolic link or the link itself. By default, symbolic links are followed. This means that without specifying LinkOption parameter, links target file attributes are read and not its own. On the other hand, if this is not the desired operation, we can direct the method on the link itself by specifying LinkOption.NOFOLLOW_LINKS
. The whole situation is presented in the example below.
Path target = Paths.get("c:/a.txt"); Path symbolicLink = Paths.get("c:/links/symbolicLink.txt"); // creates test link Files.createSymbolicLink(symbolicLink, target); BasicFileAttributes targetAttributes = Files.readAttributes(symbolicLink, BasicFileAttributes.class); BasicFileAttributes linkAttributes = Files.readAttributes(symbolicLink, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); System.out.println("File attribute - isSymbolicLink\tTarget: " + targetAttributes.isSymbolicLink() + "\t\t\t\tLink: " + linkAttributes.isSymbolicLink()); System.out.println("File attribute - size\t\tTarget: " + targetAttributes.size() + "\t\t\t\tLink: " + linkAttributes.size()); System.out.println("File attribute - creationTime:\tTarget: " + targetAttributes.creationTime() + "\tLink: " + linkAttributes.creationTime());
With an output:
File attribute - isSymbolicLink: Target: false Link: true File attribute - size: Target: 8556 Link: 0 File attribute - creationTime: Target: 2013-12-08T16:43:19.55401Z Link: 2013-12-14T16:09:17.547538Z
Is java unable to create the symbolic link that calls the referenced item with arguments?