In this tutorial, we'll learn how to construct a relative path from two absolute paths in Java. We'll focus on two built-in Java APIs – the new I/O (NIO2) Path API and the URI class.
2. Absolute vs. Relative Paths
Before we start, let's do a quick recap. For all the examples in the text, we'll use the same file structure in the user's home directory:
/ (root) |-- baeldung \-- bar | |-- one.txt | |-- two.txt \-- foo |-- three.txt
An absolute path describes a location regardless of the current working directory, starting at the root node. Here are the absolute paths for our files:
one.txt -> /baeldung/bar/one.txt two.txt -> /baeldung/bar/two.txt three.txt -> /baeldung/foo/three.txt
The absolute paths always remain the same even if we change the working directory.
On the other hand, a relative path describes the location of the target node relative to its source. If we're in the baeldung directory, let's look at the relative paths for the files:
one.txt -> ./bar/one.txt two.txt -> ./bar/two.txt three.txt -> ./foo/three.txt
Now, let's move to the bar subdirectory and check the relative paths again:
one.txt -> ./one.txt two.txt -> ./two.txt three.txt -> ../foo/three.txt
As we can see, the results are slightly different. We must remember that relative values can change if we modify the source context, while absolute paths are constant. An absolute path is a special case of a relative path, where the source node is the root of the system.
3. NIO2 API
Now that we know how relative and absolute paths work, it's time to check out the NIO2 API. As we know, the NIO2 API was introduced with the release of Java 7, and it improved the old I/O API, which had many pitfalls. Using this API, we'll try to determine the relative path between two files described by their absolute paths.
Let's start by constructing Path objects for our files:
Path pathOne = Paths.get("/baeldung/bar/one.txt"); Path pathTwo = Paths.get("/baeldung/bar/two.txt"); Path pathThree = Paths.get("/baeldung/foo/three.txt");
To construct a relative path between the source and a given node, we can use the relativize(Path) method provided by the Path class:
Path result = pathOne.relativize(pathTwo); assertThat(result) .isRelative() .isEqualTo(Paths.get("../two.txt"));
As we see, the result is definitely a relative path. Is that correct? Especially with the parent operator (../) at the beginning?
We must remember that a relative path can be specified starting from any type of node, which can be a directory or a file. Especially when we use CLI or explorers, we work with directories. Then all relative paths are calculated based on the current working directory.
In our example, we created the Path pointing to a specific file. So we first need to get to the parent of the file, its directory and then go to the second file. Overall, the result is correct.
If we want to make the result relative to the source directory, we can use the getParent() method:
Path result = pathOne.getParent().relativize(pathTwo); assertThat(result) .isRelative() .isEqualTo(Paths.get("two.txt"));
We should note that the Path object may point to any file or directory. We need to provide additional checks if we're building a more complex logic.
Finally, let's check the relative path between the one.txt and three.txt files:
Path resultOneToThree = pathOne.relativize(pathThree); Path resultThreeToOne = pathThree.relativize(pathOne); assertThat(resultOneToThree) .isRelative() .isEqualTo(Paths.get("..\..\foo\three.txt")); assertThat(result) .isRelative() .isEqualTo(Paths.get("..\..\bar\one.txt"));
This quick test confirms that the relative path is context-dependent. While the absolute paths are still the same, the relative path will be different when we swap the source and destination nodes together.
4. java.net.URI API
After checking the NIO2 API, let's go to the java.net.URI class. We know that URI (Uniform Resource Identifier) is a string of characters that allows us to identify any resource that can also be used when working with files.
Let's construct URI objects for our files:
URI uriOne = pathOne.toURI(); // URI uriOne = URI.create("file:///baeldung/bar/one.txt") URI uriTwo = pathTwo.toURI(); URI uriThree = pathThree.toURI();
We can either construct a URI object using String or convert a previously created Path.
As before, the URI class also provides a relativize(URI) method. Let's use it to construct the relative path:
URI result = uriOne.relativize(uriTwo); assertThat(result) .asString() .contains("file:///baeldung/bar/two.txt");
The result isn't what we expected, the relative path hasn't been constructed correctly. To answer the question of why this is so, we need to check the official documentation of the class.
This method only returns a relative value if the source URI is a prefix of a target URI. Otherwise, it returns the target value. As a result, we are unable to build a relative path between the file nodes. In this scenario, one URI will never prefix the other.
To return a relative path, we can set our source URI as the directory of the first file:
URI uriOneParent = pathOne.getParent().toUri(); // file:///baeldung/bar/ URI result = uriOneParent.relativize(uriTwo); assertThat(result) .asString() .contains("two.txt");
Now the source node is the target prefix, so the result is calculated correctly. Due to the limitations of the method, we're unable to determine the relative path between one.txt/two.txt and three.txt files using the URI method. Their directories will not have a common prefix.
In this article, we started out by looking at the key differences between absolute and relative paths.
Next, we constructed a relative path between two files described by their absolute paths. We started by checking the NIO2 API and detailed the relative path-building process.
Finally, we tried to achieve the same result with the java.net.URI class. We found that we can't do all transformations using this API due to its restrictions.
As always, all examples with the additional tests can be found over on GitHub.