Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

In this article, we will learn how to use the new I/O (NIO2) Path API in Java.

The Path APIs in NIO2 constitute one of the major new functional areas that shipped with Java 7 and specifically a subset of the new file system API alongside File APIs.

2. Setup

The NIO2 support is bundled in the java.nio.file package. So setting up your project to use the Path APIs is just a matter of importing everything in this package:

import java.nio.file.*;

Since the code samples in this article will probably be running in different environments, let’s get a handle on the home directory of the user:

private static String HOME = System.getProperty("user.home");

This variable will point to a valid location in any environment.

The Paths class is the main entry point to all operations involving file system paths. It allows us to create and manipulate paths to files and directories.

Worthy of note is that path operations are mainly syntactic in nature; they have no effect on the underlying file system and neither does the file system have any effect on whether they succeed or fail. This means that passing an inexistent path as a parameter of a path operation has no bearing on whether it succeed or fails.

3. Path Operations

In this section, we will introduce the main syntax used in path operations. As its name implies, the Path class is a programmatic representation of a path in the file system.

A Path object contains the file name and directory list used to construct the path and is used to examine, locate, and manipulate files.

The helper class, java.nio.file.Paths (in plural form) is the formal way of creating Path objects. It has two static methods for creating a Path from a path string:

Path path = Paths.get("path string");

Whether we use a forward or backslash in the path String, does not matter, the API resolves this parameter according to the underlying file system’s requirements.

And from a java.net.URI object:

Path path = Paths.get(URI object);

We can now go ahead and see these in action.

4. Creating a Path

To create a Path object from a path string:

@Test
public void givenPathString_whenCreatesPathObject_thenCorrect() {
    Path p = Paths.get("/articles/baeldung");
 
    assertEquals("\\articles\\baeldung", p.toString());
}

The get API can take a variable arguments parameter of path string parts (in this case, articles and baeldung) in addition to the first part (in this case, articles).

If we provide these parts instead of a complete path string, they will be used to construct the Path object, we do not need to include the name-separators (slashes) in the variable arguments part:

@Test
public void givenPathParts_whenCreatesPathObject_thenCorrect() {
    Path p = Paths.get("/articles", "baeldung");
    
    assertEquals("\\articles\\baeldung", p.toString());
}

5. Retrieving Path Information

You can think of the Path object as name elements as a sequence. A path String such as E:\baeldung\articles\java consists of three name elements i.e. baeldung, articles, and java. The highest element in the directory structure would be located at index 0, in this case being baeldung.

The lowest element in the directory structure would be located at index [n-1], where n is the number of name elements in the path. This lowest element is called the file name regardless of whether it is an actual file or not:

@Test
public void givenPath_whenRetrievesFileName_thenCorrect() {
    Path p = Paths.get("/articles/baeldung/logs");

    Path fileName = p.getFileName();
 
    assertEquals("logs", fileName.toString());
}

Methods are available for retrieving individual elements by index:

@Test
public void givenPath_whenRetrievesNameByIndex_thenCorrect() {
    Path p = Paths.get("/articles/baeldung/logs");
    Path name0 = getName(0);
    Path name1 = getName(1);
    Path name2 = getName(2);
    assertEquals("articles", name0.toString());
    assertEquals("baeldung", name1.toString());
    assertEquals("logs", name2.toString());
}

or a sub-sequence of the path using these index ranges:

@Test
public void givenPath_whenCanRetrieveSubsequenceByIndex_thenCorrect() {
    Path p = Paths.get("/articles/baeldung/logs");

    Path subPath1 = p.subpath(0,1);
    Path subPath2 = p.subpath(0,2);
 
    assertEquals("articles", subPath1.toString());
    assertEquals("articles\\baeldung", subPath2.toString());
    assertEquals("articles\\baeldung\\logs", p.subpath(0, 3).toString());
    assertEquals("baeldung", p.subpath(1, 2).toString());
    assertEquals("baeldung\\logs", p.subpath(1, 3).toString());
    assertEquals("logs", p.subpath(2, 3).toString());
}

Each path is associated with a parent path or null if the path has no parent. The parent of a path object consists of the path’s root component, if any, and each element in the path except for the file name. As an example, the parent path of /a/b/c is /a/b and that of /a is null:

@Test
public void givenPath_whenRetrievesParent_thenCorrect() {
    Path p1 = Paths.get("/articles/baeldung/logs");
    Path p2 = Paths.get("/articles/baeldung");
    Path p3 = Paths.get("/articles");
    Path p4 = Paths.get("/");

    Path parent1 = p1.getParent();
    Path parent2 = p2.getParent();
    Path parent3 = p3.getParent();
    Path parent4 = p4.getParenth();

    assertEquals("\\articles\\baeldung", parent1.toString());
    assertEquals("\\articles", parent2.toString());
    assertEquals("\\", parent3.toString());
    assertEquals(null, parent4);
}

We can also get the root element of a path:

@Test
public void givenPath_whenRetrievesRoot_thenCorrect() {
    Path p1 = Paths.get("/articles/baeldung/logs");
    Path p2 = Paths.get("c:/articles/baeldung/logs");

    Path root1 = p1.getRoot();
    Path root2 = p2.getRoot();

    assertEquals("\\", root1.toString());
    assertEquals("c:\\", root2.toString());
}

6. Normalizing a Path

Many file systems use “.” notation to denote the current directory and “..” to denote the parent directory. You might have a situation where a path contains redundant directory information.

For example, consider the following path strings:

/baeldung/./articles
/baeldung/authors/../articles
/baeldung/articles

They all resolve to the same location /baeldung/articles. The first two have redundancies while the last one does not.

Normalizing a path involves removing redundancies in it. The Path.normalize() operation is provided for this purpose.

This example should now be self-explanatory:

@Test
public void givenPath_whenRemovesRedundancies_thenCorrect1() {
    Path p = Paths.get("/home/./baeldung/articles");

    Path cleanPath = p.normalize();
 
    assertEquals("\\home\\baeldung\\articles", cleanPath.toString());
}

This one too:

@Test
public void givenPath_whenRemovesRedundancies_thenCorrect2() {
    Path p = Paths.get("/home/baeldung/../articles");

    Path cleanPath = p.normalize();
 
    assertEquals("\\home\\articles", cleanPath.toString());
}

7. Path Conversion

There are operations to convert a path to a chosen presentation format. To convert any path into a string that can be opened from the browser, we use the toUri method:

@Test
public void givenPath_whenConvertsToBrowseablePath_thenCorrect() {
    Path p = Paths.get("/home/baeldung/articles.html");

    URI uri = p.toUri();
    assertEquals(
      "file:///E:/home/baeldung/articles.html", 
        uri.toString());
}

We can also convert a path to its absolute representation. The toAbsolutePath method resolves a path against a file system default directory:

@Test
public void givenPath_whenConvertsToAbsolutePath_thenCorrect() {
    Path p = Paths.get("/home/baeldung/articles.html");

    Path absPath = p.toAbsolutePath();
 
    assertEquals(
      "E:\\home\\baeldung\\articles.html", 
        absPath.toString());
}

However, when the path to be resolved is detected to be already absolute, the method returns it as is:

@Test
public void givenAbsolutePath_whenRetainsAsAbsolute_thenCorrect() {
    Path p = Paths.get("E:\\home\\baeldung\\articles.html");

    Path absPath = p.toAbsolutePath();
 
    assertEquals(
      "E:\\home\\baeldung\\articles.html", 
        absPath.toString());
}

We can also convert any path to its real equivalent by calling the toRealPath method. This method tries to resolve the path by mapping it’s elements to actual directories and files in the file system.

Time to use the variable we created in the Setup section which points to logged-in user’s home location in the file system:

@Test
public void givenExistingPath_whenGetsRealPathToFile_thenCorrect() {
    Path p = Paths.get(HOME);

    Path realPath = p.toRealPath();
 
    assertEquals(HOME, realPath.toString());
}

The above test does not really tell us much about the behavior of this operation. The most obvious result is that if the path does not exist in the file system, then the operation will throw an IOException, read on.

For the lack of a better way to drive this point home, just take a look at the next test, which attempts to convert an inexistent path to a real path:

@Test(expected = NoSuchFileException.class)
public void givenInExistentPath_whenFailsToConvert_thenCorrect() {
    Path p = Paths.get("E:\\home\\baeldung\\articles.html");
    
    p.toRealPath();
}

The test succeeds when we catch an IOException. The actual subclass of IOException that this operation throws is NoSuchFileException.

8. Joining Paths

Joining any two paths can be achieved using the resolve method.

Simply put, we can call the resolve method on any Path and pass in a partial path as the argument. That partial path is appended to the original path:

@Test
public void givenTwoPaths_whenJoinsAndResolves_thenCorrect() {
    Path p = Paths.get("/baeldung/articles");

    Path p2 = p.resolve("java");
 
    assertEquals("\\baeldung\\articles\\java", p2.toString());
}

However, when the path string passed to the resolve method is not a partial path; most notably an absolute path, then the passed-in path is returned:

@Test
public void givenAbsolutePath_whenResolutionRetainsIt_thenCorrect() {
    Path p = Paths.get("/baeldung/articles");

    Path p2 = p.resolve("C:\\baeldung\\articles\java");
 
    assertEquals("C:\\baeldung\\articles\\java", p2.toString());
}

The same thing happens with any path that has a root element. The path string “java” has no root element while the path string “/java” has a root element. Therefore, when you pass in a path with a root element, it is returned as is:

@Test
public void givenPathWithRoot_whenResolutionRetainsIt_thenCorrect2() {
    Path p = Paths.get("/baeldung/articles");

    Path p2 = p.resolve("/java");
 
    assertEquals("\\java", p2.toString());
}

9. Relativizing Paths

The term relativizing simply means creating a direct path between two known paths. For instance, if we have a directory /baeldung and inside it, we have two other directories such that /baeldung/authors and /baeldung/articles are valid paths.

The path to articles relative to authors would be described as “move one level up in the directory hierarchy then into articles directory” or ..\articles:

@Test
public void givenSiblingPaths_whenCreatesPathToOther_thenCorrect() {
    Path p1 = Paths.get("articles");
    Path p2 = Paths.get("authors");

    Path p1_rel_p2 = p1.relativize(p2);
    Path p2_rel_p1 = p2.relativize(p1);
 
    assertEquals("..\\authors", p1_rel_p2.toString());
    assertEquals("..\\articles", p2_rel_p1.toString());
}

Assuming we move the articles directory to authors folder such that they are no longer siblings. The following relativizing operations involve creating a path between baeldung and articles and vice versa:

@Test
public void givenNonSiblingPaths_whenCreatesPathToOther_thenCorrect() {
    Path p1 = Paths.get("/baeldung");
    Path p2 = Paths.get("/baeldung/authors/articles");

    Path p1_rel_p2 = p1.relativize(p2);
    Path p2_rel_p1 = p2.relativize(p1);
 
    assertEquals("authors\\articles", p1_rel_p2.toString());
    assertEquals("..\\..", p2_rel_p1.toString());
}

10. Comparing Paths

The Path class has an intuitive implementation of the equals method which enables us to compare two paths for equality:

@Test
public void givenTwoPaths_whenTestsEquality_thenCorrect() {
    Path p1 = Paths.get("/baeldung/articles");
    Path p2 = Paths.get("/baeldung/articles");
    Path p3 = Paths.get("/baeldung/authors");

    assertTrue(p1.equals(p2));
    assertFalse(p1.equals(p3));
}

You can also check if a path begins with a given string:

@Test
public void givenPath_whenInspectsStart_thenCorrect() {
    Path p1 = Paths.get("/baeldung/articles");
 
    assertTrue(p1.startsWith("/baeldung"));
}

Or ends with some other string:

@Test
public void givenPath_whenInspectsEnd_thenCorrect() {
    Path p1 = Paths.get("/baeldung/articles");
  
    assertTrue(p1.endsWith("articles"));
}

11. Conclusion

In this article, we showed Path operations in the new file system API (NIO2) that was shipped as a part of Java 7 and saw most of them in action.

The code samples used in this article can be found in the article’s Github project.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.