Trail: Essential Classes
Lesson: Basic I/O
Section: File I/O (featuring NIO.2)
Subsection: The Path Class
Path Operations
Home Page > Essential Classes > Basic I/O
Path Operations
The Path class includes a variety of methods that can be used to get information about the path, access components of the path, to convert the path to other forms, or to extract portions of a path. There are methods for matching and methods for removing redundancies in a path. This lesson addresses these Path methods, sometimes called syntactic operations, because they operate on the path itself and don't necessarily access the file system.

This section covers:

Creating a Path

A Path instance contains the information used to specify the location of a file or directory. At the time it is defined, a Path is provided with a series of one or more names — a root component or a file name might be included but neither are required. A Path may simply consist of a single directory or file name.

You can easily create a Path object using one of the get methods from the Paths (note the plural) helper class:

Path p1 = Paths.get("/tmp/foo");
Path p2 = Paths.get(args[0]);
Path p3 = Paths.get("file:///Users/joe/FileTest.java");

The Paths.get method is shorthand for the following code:

Path p4 = FileSystems.getDefault().getPath("/users/sally");

Retrieving Information about the Path

You can think of the Path storing these name elements as a sequence. The element highest up in the directory structure would be located at index 0. The element lowest in the directory structure would be located at index [n-1], where n is the number of name elements in the Path. You will see that there are methods for retrieving individual components or a subsequence of the Path using these indexes.

The examples in this section will use this directory structure:

Sample directory structure

Sample directory structure.

The following code snippet which defines a Path instance and then invokes a number of methods to obtain information about the path:

// None of these methods requires that the file corresponding
// to the Path exists, with the possible exception of isHidden.
Path path = Paths.get("C:\\home\\joe\\foo");    // Microsoft Windows syntax
//Path path = Paths.get("/home/joe/foo");       // Solaris syntax
System.out.format("toString: %s%n", path.toString());
System.out.format("getName: %s%n", path.getName());
System.out.format("getName(0): %s%n", path.getName(0));
System.out.format("getNameCount: %d%n", path.getNameCount());
System.out.format("subpath(0,2): %d%n", path.subpath(0,2));
System.out.format("getParent: %s%n", path.getParent());
System.out.format("getRoot: %s%n", path.getRoot());
System.out.format("isHidden: %s%n", path.isHidden());
Here is the output for both Windows and Solaris:

Method Invoked Returns on Microsoft Windows Returns on Solaris Comment
toString C:\home\joe\foo /home/joe/foo Returns the string representation of the Path. If the path was created using Filesystems.getDefault().getPath(String) or Paths.get (the latter is a convenience method for getPath) the method will perform minor syntactic cleanup. For example, on Unix it will correct the input string //home/joe/foo to /home/joe/foo.
getName foo foo Returns the file name, or the last element of the sequence of name elements.
getName(0) home home Returns the path component corresponding to the specified index. The 0th element is the path component closest to the root.
getNameCount 3 3 Returns the number of components in the path.
subpath(0,2) home\joe home/joe Returns the subsquence of the Path (not including a root component) as specified by the beginning and ending indexes.
getParent \home\joe /home/joe Returns the path of the parent directory.
getRoot C:\ / Returns the root of the path.
isHidden false false Returns true if the file is considered hidden for a particular file system. On Solaris, hidden files are prefixed by a dot ("."). On Microsoft Windows, hidden files are specified by a file attribute, so accessing the file system is necessary to check the HIDDEN attribute.

The previous example shows the output for an absolute path. This time, a relative path is specified:

Path path = Paths.get("sally\\bar");    // Microsoft Windows syntax
or
Path path = Paths.get("sally/bar");     // Solaris syntax

Here is the output for Windows and Solaris:

Method Invoked Returns on Microsoft Windows Returns on Solaris
toString sally\bar sally/bar
getName bar bar
getName(0) sally sally
getNameCount 2 2
subpath(0,1) sally sally
getParent sally sally
getRoot null null
isHidden false false

Removing Redundancies from a Path

Many file systems use "." notation to denote the current directory and ".." to denote the parent directory. You may have a situation where a Path contains redundant directory information. Perhaps a server is configured to save its log files in the "/dir/logs/." directory and you want to delete the trailing "/." notation from the path.

The following examples both include redundancies:

/home/./joe/foo
/home/sally/../joe/foo
The normalize method removes any redundant elements, which includes any occurrences of "." or "directory/..". Both of the preceding examples normalize to /home/joe/foo.

It is important to note that normalize doesn't look at the file system when it cleans up a path — it is a purely syntactic operation. In the second example, if sally were a symbolic link, removing sally/.. may result in a Path that no longer locates the intended file.

To clean up a path where you want to ensure that the result locates the correct file, you can use the toRealPath method. This method is described in the next section: Converting the Path.

Converting the Path

There are several methods you can use to convert the Path. If you need to convert the path to a string that can be opened from a browser, you can use toUri:
Path p1 = Paths.get("/home/logfile");
System.out.format("%s%n", p1.toUri());  // Result is file:///home/logfile

The toAbsolutePath method converts a path to an absolute path. If the passed-in path is already absolute, it returns the same Path object. The toAbsolutePath method can be very handy when processing user-entered file names:

public class FileTest {
    public static void main(String[] args) throws IOException {

	if (args.length < 1) {
	    System.out.println("usage: FileTest file");
	    System.exit(-1);
	}

	// Converts the input string to a Path object.
	Path inputPath = Paths.get(args[0]);

	// Converts the input Path to an absolute path.
	// Generally, this means prepending the current  working
	// directory.  If this example were called like this:
	//	       java FileTest foo
	// the getRoot and getParent methods would return null
	// on the original "inputPath" instance.  Invoking getRoot and
	// getParent on the "fullPath" instance returns expected values.
	Path fullPath = inputPath.toAbsolutePath();
    }
}

The toAbsolutePath method converts the user input and returns a Path that returns useful values when queried. The file does not need to exist for this method to work.

The toRealPath method returns the real path of an existing file. This method performs several operations in one:

This method throws an exception if the file does not exist or cannnot be accessed — you can catch the exception when you want to handle any of these cases. For example:

try {
    Path fp = path.ToRealPath(true);
} catch (NoSuchFileException x) {
    System.err.format("%s: no such file or directory%n", path);
    //Logic for case when file doesn't exist.
} catch (IOException x) {
    System.err.format("%s%n", x);
    //Logic for other sort of file error.
}

Joining Two Paths

You can combine paths together using the resolve method. You pass in a partial path — one that does not include a root element — and that partial path is appended to the original path.

For example, the following code snippet:

Path p1 = Paths.get("/home/joe/foo");          // Solaris
System.out.format("%s%n", p1.resolve("bar"));  // Result is /home/joe/foo/bar

or

Path p1 = Paths.get("C:\\home\\joe\\foo");    // Microsoft Windows
System.out.format("%s%n", p1.resolve("bar")); // Result is C:\home\joe\foo\bar
Passing an absolute path to resolve simply returns the passed-in path:
Paths.get("foo").resolve("/home/joe");       // Result is /home/joe

Creating a Path Between Two Paths

A very common requirement when writing file I/O code is the ability to construct a path from one location on the file system to another location. You can achieve this using the relativize method. This method constructs a path originating from the original path and ending at the location specified by the passed-in path — the new path is relative to the original path.

For example, consider two relative paths defined as joe and sally:

Path p1 = Paths.get("joe");
Path p2 = Paths.get("sally");
In the absence of any other information, it is assumed that joe and sally are siblings — nodes that live at the same level in the tree structure. To travel from joe to sally you would expect to first travel one level up to the parent node and then down to sally:
Path p1_to_p2 = p1.relativize(p2);   // Result is ../sally
Path p2_to_p1 = p2.relativize(p1);   // Result is ../joe
Consider a slightly more complicated scenario:
Path p1 = Paths.get("home");
Path p3 = Paths.get("home/sally/bar");
Path p1_to_p3 = p1.relativize(p3);  // Result is sally/bar
Path p3_to_p1 = p3.relativize(p1);  // Result is ../..
In this case, the two paths share the same node, home. To travel from home to bar you first navigate one level down to sally and then one more level down to bar. To travel from bar to sally requires moving up two levels.

A relative path cannot be constructed if only one of the paths includes a root component. If both paths include a root component the ability to construct a relative path is system dependent.

The recursive Copy example uses the relativize and resolve methods.

Comparing a Path

The Path class supports equals allowing you to test two paths for equality. The startsWith and endsWith methods allow you to test whether a path begins or ends with a particular string. These methods are very easy to use:

Path path = ...;
Path p1 = ...;
Path p2 = Paths.get("/home");
Path p3 = Paths.get("foo");

if (path.equals(p3)) {
    //equality logic here
} else if (path.startsWith(p1)) {
    //path begins with "/home"
} else if (path.endsWith(p2)) {
    //path ends with "foo"
}

The Path class implements the Iterable interface — the iterator method returns an object that allows you to iterate over the name elements in the path. The first element returned is that closest to the root in the directory tree. The following code snippet iterates over a path, printing out each name element:

Path path = ...;
for (Path name: path) {
    System.out.println(name);
}

The Path class also implements the Comparable interface — you can compare Path objects using compareTo which is useful for sorting.

You can also put Path objects into a Collection. See the Collections trail for more information on this powerful feature.

When you want to verify that two Paths locate the same file, you can use the isSameFile method, described in Checking Whether Two Paths Locate the Same File.

Verifying the Existence of a File or Directory

Most of the methods described in this page are syntactic — meaning they operate on the Path instance. But eventually you will want to go to the file system to verify that a particular Path exists, or does not exist. You can do this with the exists and the notExists methods. Note that !path.exists() is not equivalent to path.notExists(). When testing a file's existence, three results are possible: If both exists and notExists returns false, the existence of the file cannot be verified.

The Path class also supports a more powerful method to check accessibility, the checkAccess method, described in Checking File Accessibility.

Previous page: The Path Class
Next page: File Operations