Skip to content

File and Directory Names in Java: File, Path, Paths

Colorful hanging files with labeled tabs

After covering file reading and writing operations in Java, this third part of the series of articles shows how to use the classes File, Path, and Paths to construct file and directory paths – regardless of the operating system.

If you have already dealt with file operations in Java, you have probably used these classes to pass a filename to one of the read or write operations with new File(), Paths.get() or Path.of().

Most programmers do not study these classes in much more detail. This is partly because these classes can be confusing even for experienced Java programmers: What is the difference between File.getName() and File.getPath()? What is the difference between File.getAbsolutePath(), File.getCanonicalPath() and Path.normalize()? What is the difference between Paths.get() and Path.of()?

This article answers the following questions:

  • What is the difference between a filename, a directory name, and a path?
  • How to construct a relative directory or file path independent of the operating system?
  • How to construct an absolute directory or file path independent of the operating system?
  • What changed with the introduction of the NIO.2 File API?
  • What exactly is returned by the File methods getName(), getPath(), getParent(), getParentFile(), getAbsolutePath(), getAbsoluteFile(), getCanonicalPath(), getCanonicalFile()?
  • What is returned by the Path methods getFileName(), getName(int index), getParent(), getRoot(), toAbsolutePath() and normalize()?
  • How to join Path objects with Path.resolve() and Path.resolveSibling()?
  • When to use File and when to use Path? And can I convert the two into each other?

Basics: Definitions of terms, operating system independence, NIO.2

What is the difference between filename, directory name, relative path, and absolute path?

Before we start, we need to agree on terminology. For example, the terms “path” and “directory” are often confused.

  • Filename: the name of a file without a directory and separators, e.g., readme.txt or system.log

  • Directory name: the name of a particular directory without parent directory(s), for example, apache-maven-3.6.3 or log

  • Path: the “route” to an object of the file system, i.e., to files and directories. A path can be absolute or relative:

    • An absolute path is always unique and independent of the current position in the file system.
    • A relative path is related to the current position within the file system. It describes how to get from this position to the target. If the target is located in the current directory, the relative path usually equals the file or directory name.
Filename, directory name, absolute and relative path, using /var/log/system.log as an example
A path is the whole route; a name is only its last element

The following table shows some examples of absolute and relative paths to directories and files – for both Windows and Linux/Mac:

Linux / MacWindows
Absolute path to a directory:/var/logC:\Windows
Absolute path to a file:/var/log/system.logC:\Windows\explorer.exe
Relative path to a directory:../../var/log (from /home/user to /var/log)..\Windows (from C:\Users to C:\Windows)
Relative path to a file:../../var/log/system.log (from /home/user to /var/log/system.log)..\Windows\explorer.exe (from C:\Users to C:\Windows\explorer.exe)

Operating system independent path and directory names

As seen in the table, absolute paths on Windows start with a drive letter and a colon; directories are separated by a backslash (\). On Linux and Mac, absolute paths start with a forward slash (/), which also separates directories.

You can access the separator of the currently used operating system with the constant File.separator or the method FileSystems.getDefault().getSeparator() and thus generate pathnames “manually” (i.e. by concatenating strings). For example like this:

String homePath = System.getProperty("user.home");
String fileName = "test" + System.currentTimeMillis();
String filePath = homePath + File.separator + fileName;
System.out.println("filePath = " + filePath);

Depending on the operating system we get a different output:

  • Windows: filePath = C:\Users\svenw\test1578950760671
  • Linux: filePath = /home/sven/test1578950836130

With the home directory, this works quite well, but with the temp directory (we get this from the system property “java.io.tmpdir”) we get the following output:

  • Windows: filePath = C:\Users\svenw\AppData\Local\Temp\\test1578950862590
  • Linux: filePath = /tmp/test1578950884314

Did you spot the problem?

On Windows, the path for the temporary directory already ends with a backslash. By adding a separator, our code creates a double backslash in the file path.

We could now check if the directory name already ends with a separator and add it only if it is not present. But that is not necessary at all. You can construct file and directory names much more elegantly – completely without string operations, using the class java.io.File and (from Java 7) the interface java.nio.file.Path.

“Old” Java File API vs. NIO.2 File API

In Java 7, the “NIO.2 File API” was introduced with JSR 203 (NIO stands for “New I/O”). This provides a whole new set of classes for handling files (introduced in the previous article about writing and reading files).

Files and directories were previously represented by the class java.io.File. This leads to confusion, especially for beginners, because the class name suggests that the class only represents files, not directories.

In NIO.2, this task is taken over by the – now appropriately named – interface java.nio.file.Path. Its methods have been completely redesigned compared to java.io.File.

Constructing file and directory paths with java.io.File

Let’s start with the “old” class, java.io.File. A file object can represent a filename, a directory name, a relative file or directory path, or an absolute file or directory path (where a file/directory name is actually also a relative file/directory path, relative to the directory in which the file/directory is located).

java.io.File: File and directory names

Representing file names with java.io.File

You can define a filename (without specifying a directory) as follows (we stick to the pattern “test<timestamp>” used above):

File file = new File("test" + System.currentTimeMillis());

You can now read the following information from the File object:

MethodReturn value
file.getName()test1578953190701
file.getPath()test1578953190701
file.getParent() / file.getParentFile()null
file.getAbsolutePath() / file.getAbsoluteFile()/happycoders/git/filedemo/test1578953190701

The method getName() returns the filename we passed to the constructor. getPath() returns the path, which in this case corresponds to the filename since we have not specified a directory. For the same reason, getParent() and getParentFile() both return null, the first method returns a String, the second a corresponding File object.

Using the methods getAbsolutePath() and getAbsoluteFile(), you map this file into the current working directory and get the complete path of the file including its directory and filename. These two methods also differ only in that the first returns a String, and the second returns a corresponding File object.

There are also the methods getCanonicalPath() and getCanonicalFile(), which would return the same values as getAbsolutePath() and getAbsoluteFile() in this and the following examples. We will see in a later example, in which cases they can contain other values.

Representing directory names with java.io.File

The File object constructed in the previous section could just as well represent a directory with the same name instead of a file. The methods listed in the table above would return the same results.

A distinction would only be possible if a file or directory with this name already exists. If a corresponding file exists, the method file.isFile() returns true. If, on the other hand, a directory with this name exists, the method file.isDirectory() returns true. If neither file nor directory exists, both methods return false. Depending on the further use, the File object can then be used either to create a directory or to create a file.

java.io.File: Relative file and directory paths

Relative file path with java.io.File

To specify a directory, we can pass it to the File constructor as a parameter – in the simplest form as a String. With the following code, you put the test file into a tests directory:

File file = new File("tests", "test" + System.currentTimeMillis());

The getters now provide the following information about the File object (with the differences to the previous example highlighted in bold):

MethodReturn value
file.getName()test1578953190701
file.getPath()tests/test1578953190701
file.getParent() / file.getParentFile()tests
file.getAbsolutePath() / file.getAbsoluteFile()/happycoders/git/filedemo/tests/test1578953190701

We can now see a difference between getName() and getPath(): the first method returns only the file name without the directory information, the second method returns the complete relative path. getParent() and getParentFile() (remember: the first method returns a String, the second a corresponding File object) now return the specified directory tests. In the absolute path information returned by getAbsolutePath() and getAbsoluteFile() (again: String vs. File), the subdirectory tests gets inserted accordingly.

The directory can also be passed as a File object instead of a String:

File directory = new File("tests");
File file = new File(directory, "test" + System.currentTimeMillis());

Relative file path with nested directories

Several directory levels can also be nested:

File testsDirectory = new File("tests");
File yearDirectory = new File(testsDirectory, "2020");
File dayDirectory = new File(yearDirectory, "2020-01-13");
File file = new File(dayDirectory, "test" + System.currentTimeMillis());

This allows us to construct directory paths of any depth without having to use the separator character even once. The example constructs a file object with the path tests/2020/2020-01-13/test1578953190701.

What will the getParent() method return now? tests/2020/2020-01-13 or just 2020-01-13? Let’s try it…

MethodReturn value
file.getName()test1578953190701
file.getPath()tests/2020/2020-01-13/test1578953190701
file.getParent() / file.getParentFile()tests/2020/2020-01-13
file.getAbsolutePath() / file.getAbsoluteFile()/happycoders/git/filedemo/tests/2020/2020-01-13/test1578953190701

The parent, therefore, represents the path to the parent directory, not just its name. To access the name of the parent directory we can use file.getParentFile().getName().

Relative directory path with java.io.File

Also, in the examples from the previous section, test1578953190701 could be a directory instead of a file. The path does not allow any conclusions to be drawn.

java.io.File: Absolute file and directory paths

Absolute directory path with java.io.File

Attention: We look at absolute paths in reverse order: We first construct the directory path and then the file path, since we cannot generate an absolute file path without an absolute directory path as a parent.

We have the following options for constructing an absolute directory path:

  • from an absolute directory path represented as a String (therefore dependent on the operating system)
  • from the system properties mentioned at the beginning, like “user.home” and “java.io.tmpdir”
  • from the current directory

Constructing an absolute directory path from a String

Once we have the absolute directory path in a String (for example, from a configuration file), we can pass it directly to the File constructor. The following example uses a String constant for simplicity:

File directory = new File("/var/log/myapp");

For this absolute directory, the File object’s getters return the following values:

MethodReturn value
file.getName()myapp
file.getPath()/var/log/myapp
file.getParent() / file.getParentFile()/var/log
file.getAbsolutePath() / file.getAbsoluteFile()/var/log/myapp

The Methods getPath(), getAbsolutePath() and getAbsoluteFile() now all return the absolute path of the directory. getParent() and getParentFile() return the absolute path of the parent directory.

Constructing an absolute directory path from system properties

Through the system properties user.home and java.io.tmpdir, you get – independent of the operating system – the user’s home directory and the temporary directory.

The System.getProperty() method finally returns the path as a String, which we can then pass to the File constructor. Above, we saw that on Windows, the temporary directory has a trailing backslash, the home directory does not. Does that cause us problems at this point?

We can use the following code to test it on Windows:

String homeDir = System.getProperty("user.home");
System.out.println("homeDir                = " + homeDir);
System.out.println("homeDir as File object = " + new File(homeDir));

String tempDir = System.getProperty("java.io.tmpdir");
System.out.println("tempDir                = " + tempDir);
System.out.println("tempDir as File object = " + new File(tempDir));

The program delivers the following output:

homeDir                = C:\Users\svenw
homeDir as File object = C:\Users\svenw
tempDir                = C:\Users\svenw\AppData\Local\Temp\
tempDir as File object = C:\Users\svenw\AppData\Local\Temp

The unnecessary trailing backslash has been removed, so we don’t have to worry about anything.

Creating an absolute directory path from the current directory

Using what we know so far, we could construct the absolute directory path of the current directory as follows:

File file = new File("dummy");
File absoluteFile = file.getAbsoluteFile();
File absoluteDirectory = absoluteFile.getParentFile();

Here we have generated a dummy file path, constructed the absolute path for it (which maps the file into the current directory), and then extracted the parent – the absolute path of the directory in which the file is located.

Admittedly, this is rather cumbersome. Fortunately, there is a more elegant way: The current directory can also be read from a system property, user.dir, and then passed to the File constructor:

File currentDir = new File(System.getProperty("user.dir"));

Absolute file path with java.io.File

After having looked at different ways of creating an absolute directory path, we can now construct an absolute file path. All we have to do is pass the directory and filename to the File constructor.

File tempDir = new File(System.getProperty("java.io.tmpdir"));
File subDir = new File(tempDir, "myapp");
String fileName = "foo";
File file = new File(subDir, fileName);

For this example, the getters of the File object return the following values:

MethodReturn value
file.getName()foo
file.getPath()/tmp/myapp/foo
file.getParent() / file.getParentFile()/tmp/myapp
file.getAbsolutePath() / file.getAbsoluteFile()/tmp/myapp/foo

We see a similar pattern as in the example with the absolute directory path /var/log/myapp: The methods getPath(), getAbsolutePath() and getAbsoluteFile() return the absolute path of the file. And getParent() and getParentFile() return the absolute path of the parent directory.

java.io.File: Overview of File’s getter methods

The following table summarizes what File’s getters return depending on the file system object represented:

MethodFile/directory nameRelative file/directory pathAbsolute file/directory path
getName()File/directory nameFile/directory nameFile/directory name
getPath()File/directory nameRelative file/directory pathAbsolute file/directory path
getParent() / getParentFile()nullRelative path to parent directoryAbsolute path to parent directory
getAbsolutePath() / getAbsoluteFile()Absolute path from combination of current directory and file/directory nameAbsolute path from combination of current directory and relative file/directory pathAbsolute file/directory path

Once again as a reminder: getParent() and getAbsolutePath() return a String; getParentFile() and getAbsoluteFile() return a corresponding File object.

java.io.File: What is the difference between getCanonicalPath() / getCanonicalFile() and getAbsolutePath() / getAbsoluteFile()?

In all previous examples, the methods getCanonicalPath() and getCanonicalFile() would have returned the same result as getAbsolutePath() and getAbsoluteFile() – namely the respective absolute path (as String or File object).

So what is the difference?

A “canonical path” is unique; i.e., there is only one such path to a file. In contrast, there can be more than one absolute path to the same file. An example:

For the file /var/log/system.log, this String is also the “canonical path”. The same String is also an absolute path. However, there are other absolute paths, such as

  • /var/log/./system.log,
  • /var/log/../log/system.log,
  • /home/user/../../var/log/system.log,
  • as well as all paths that eventually point to /var/log/system.log via symbolic links.

The File constructor accepts all of these spellings. Only getCanonicalPath() resolves them:

File file = new File("/var/log/../log/./system.log");
System.out.println(file.getAbsolutePath());
System.out.println(file.getCanonicalPath());

The output:

/var/log/../log/./system.log
/var/log/system.log

To do so, getCanonicalPath() has to query the file system – to resolve symbolic links – and can therefore throw an IOException. getAbsolutePath() works on the string alone.

Constructing file and directory paths with java.nio.file.Path and Paths

Although the interface of java.nio.file.Path has been completely changed from java.io.File, the underlying concepts have remained unchanged. Filenames, directory names, relative file and directory paths, and absolute file and directory paths are still the file system objects being represented.

What was changed? In the following sections, you see how to construct Path objects, using the same structure and the same examples as with the File objects before.

In the following sections, I will not make a distinction between file and directory names. We have already seen above that, as long as the corresponding file system object does not exist, its path does not indicate whether it is a file or a directory.

java.nio.file.Path: File and directory names

Instead of a constructor, we use a factory method for java.nio.file.Path to create instances. Why? Path is an interface, and static methods in interfaces have only existed since Java 8. That is why NIO.2 needed the helper class java.nio.file.Paths (with an “s” at the end) for the factory method in Java 7. Since Java 11, it is also available directly in the interface as Path.of(). We create a Path object for the file name “test<timestamp>” as follows:

Path path = Paths.get("test" + System.currentTimeMillis());

The same with Path.of() – there is no difference in behavior. Internally, Paths.get() calls the newer method Path.of(), and this in turn calls FileSystems.getDefault().getPath(). However, the Javadoc of Paths explicitly recommends Path.of() and announces that Paths may be deprecated in a future release. So in new code, use Path.of():

Path path = Path.of("test" + System.currentTimeMillis());

The following methods provide information analogous to methods of the File class shown above:

MethodReturn value
path.getFileName()test1579037366379
path.getNameCount()1
path.getName(0)test1579037366379
path.getParent()null
path.getRoot()null
path.toAbsolutePath()/happycoders/git/filedemo/test1579037366379
path.normalize()test1579037366379

First of all: All methods shown – except for getNameCount() – return a Path object. There are no variants of these methods that return a String. The only way to convert a Path object into a String is to call its toString() method.

The getFileName() method returns the name of the file or directory. getNameCount() is always 1 for a filename without directory information, or for directories without a parent directory. And the first name, retrievable by getName(0), is also the file/directory name. getParent() returns – like File’s method with the same name – null. The method getRoot() also returns null, since a single filename is always relative. With toAbsolutePath() we map – analogous to File.getAbsolutePath() – the file/directory into the current directory and get the corresponding absolute path.

Path.normalize() removes redundant elements such as . and .. purely syntactically, without consulting the file system as File.getCanonicalPath() does; relative paths stay relative. The counterpart of File.getCanonicalPath() is Path.toRealPath(): It returns the absolute path, resolves symbolic links, and requires the file to exist.

java.nio.file.Path: Relative file and directory paths

To include directories, Path offers two different approaches:

  • You can list all directories in the factory method Paths.get() or Path.of().
  • You can use its resolve() method to convert an existing Path object into a new Path object, where the resolve() method is equivalent to changing the directory with the Windows or Linux cd command – and just like with cd, an absolute path as the parameter replaces the previous path entirely.

Constructing relative file and directory paths with Paths.get() / Path.of()

You can pass any number of directory names to the factory methods. For example, you would construct the relative path tests/test1578953190701 as follows (the timestamp is hard-coded for simplicity):

Path path = Paths.get("tests", "test1578953190701");

This Path object’s getters return the following results (I highlighted the differences to the individual file names in bold again):

MethodReturn value
path.getFileName()test1578953190701
path.getNameCount()2
path.getName(0)tests
path.getName(1)test1578953190701
path.getParent()tests
path.getRoot()null
path.toAbsolutePath()/happycoders/git/filedemo/tests/test1578953190701
path.normalize()tests/test1578953190701

Due to the directory, nameCount has increased from 1 to 2. In the name array, the directory name was inserted at position 0; the file name has moved to position 1. getParent() also returns the directory name. The absolute path and the normalized version also contain the directory name.

Constructing nested file and directory paths with Paths.get() / Path.of()

If the file is to be located in the directory tests/2020/2020-01-13, call the factory method as follows:

Path path = Paths.get("tests", "2020", "2020-01-13", "test1578953190701");

Here once again, the getters’ results:

MethodReturn value
path.getFileName()test1578953190701
path.getNameCount()4
path.getName(0)tests
path.getName(1)2020
path.getName(2)2020-01-13
path.getName(3)test1578953190701
path.getParent()tests/2020/2020-01-13
path.getRoot()null
path.toAbsolutePath()/happycoders/git/filedemo/tests/2020/2020-01-13/test1578953190701
path.normalize()tests/2020/2020-01-13/test1578953190701

nameCount has been increased accordingly, and the name array contains all directories of the path as well as the filename. Again, getParent() returns the complete known path to the directory, not just its name.

Constructing relative file and directory paths with Path.resolve()

An alternative way to construct the Path object for the path tests/2020/2020-01-13/test1578953190701 is to use the resolve() method. It combines the path on which the method is called with the path passed to it:

Path testsDir = Paths.get("tests");
Path yearDir = testsDir.resolve("2020");
Path dayDir = yearDir.resolve("2020-01-13");
Path path = dayDir.resolve("test1578953190701");

The resolve() operation is associative, i.e., the individual parts can also be joined in a different order, for example like this:

Path testsDir = Paths.get("tests"); // tests
Path yearDir = testsDir.resolve("2020"); // tests/2020
Path dayDir = Paths.get("2020-01-13"); // 2020-01-13

// 2020-01-13/test1578953190701
Path fileInDayDir = dayDir.resolve("test1578953190701");

// tests/2020/2020-01-13/test1578953190701
Path path = yearDir.resolve(fileInDayDir);

Shortcut: Path.resolveSibling()

Let’s assume we have the Path object we constructed in the previous section, and we want to create another file in the same directory. If we still have access to the Path object representing the directory, we can call resolve() on it with the new filename. Otherwise, we could access the directory with getParent() and then call resolve():

Path sibling = path.getParent().resolve("test" + System.currentTimeMillis());

Exactly for this purpose, there is the shortcut resolveSibling(), which saves you five keystrokes and bytes:

Path sibling = path.resolveSibling("test" + System.currentTimeMillis());

java.nio.file.Path: Absolute file and directory paths

Similar to java.io.File, we can also create an absolute path with java.nio.file.Path from a String that we read from a configuration file or a system property. We can pass this path, as it is, to the factory method. We do not need to split it up first. Using the directory /var/log/myapp as an example again:

Path path = Paths.get("/var/log/myapp");

Of course, we can also modify an absolute path using the resolve() method. The following example corresponds to the example of the absolute file path with java.io.File:

Path tempDir = Path.of(System.getProperty("java.io.tmpdir"));
Path subDir = tempDir.resolve("myapp");
String fileName = "foo";
Path file = subDir.resolve(fileName);

For this absolute path, the getter methods return:

MethodReturn value
path.getFileName()foo
path.getNameCount()3
path.getName(0)tmp
path.getName(1)myapp
path.getName(2)foo
path.getParent()/tmp/myapp
path.getRoot()/
path.toAbsolutePath()/tmp/myapp/foo
path.normalize()/tmp/myapp/foo

Here we experience for the first time that path.getRoot() does not return null, but /, the Linux root directory. On Windows, we would get C:\ here (unless the temporary directory is in a different file system). The root directory is not part of the name array.

Anatomy of a Path object: getRoot(), getName(0) to getName(2), getParent() and getFileName(), using /tmp/myapp/foo as an example
getRoot() lies outside the name array: getNameCount() counts only the names

java.nio.file.Path: Overview of its getter methods

Here you can see a summary of what Path’s getters return:

MethodFile/directory nameRelative file/directory pathAbsolute file/directory path
path.getFileName()File/directory nameFile/directory nameFile/directory name
path.getNameCount()1Number of directories + fileNumber of directories + file
path.getName(index)File/directory nameFile/directory names at the given position (0-based)File/directory names at the given position (0-based, root does not count)
path.getParent()nullRelative path to parent directoryAbsolute path to parent directory
path.getRoot()nullnullThe file system’s root, such as / or C:\
path.toAbsolutePath()Absolute path from combination of current directory and file/directory nameAbsolute path from combination of current directory and relative file/directory pathAbsolute file/directory path
path.normalize()Normalized file/directory nameNormalized relative file/directory pathNormalized absolute file/directory path

File vs. Path: the methods compared

You have now seen both types on their own. Here is which File method corresponds to which Path method:

java.io.Filejava.nio.file.Path
new File(dir, name)Path.of(dir, name) or dir.resolve(name)
getName()getFileName()
getPath()toString()
getParent() / getParentFile()getParent()
getAbsolutePath() / getAbsoluteFile()toAbsolutePath()
getCanonicalPath() / getCanonicalFile()toRealPath()
normalize()
getRoot(), getNameCount(), getName(index)
toPath()toFile()

Two things stand out: Path has no String variants of its getters – every call returns another Path object. And Path can split and normalize paths without consulting the file system; File cannot.

So when to use File and when Path? My recommendation: in new code, use Path only. You need File only where an older API demands a File object – and for that, there is Path.toFile(). The other way around, File.toPath() gives you a Path object from an existing File object.

Summary and outlook

This article gave a detailed overview of how to construct file and directory paths independent of the operating system using the classes File, Path, and Paths.

My recommendation is to use only the NIO.2 types Path and Paths from Java 7 on, and from Java 11 on, use only Path. If you need a File object for a particular file operation, you can always create one with Path.toFile().

The following parts of the series will deal with the following topics:

In the later course we come to advanced topics:

  • NIO channels and buffers introduced in Java 1.4, to speed up working with large files
  • Memory-mapped I/O for blazing-fast file access without streams
  • File locking, to access the same files in parallel – i.e., from several threads or processes – without conflicts

If this article has helped you, I would greatly appreciate a positive review on my ProvenExpert profile. Your feedback helps me improve my content and motivates me to write new informative articles.

👉 Leave a review

Want to stay up to date on all new Java features? Then click here to sign up for the HappyCoders newsletter.

👉 Newsletter Sign-up

Want Even More Knowledge?

My blog features many articles on Java, software architecture, and performance — from foundational concepts to advanced patterns.

If you want to go deeper, check out my trainings: hands-on, easy to understand, and directly applicable to your day-to-day project work. Instead of theory, I teach principles that help you write code that is better, more maintainable, and more performant in the long run.

Explore the Java Trainings

Become a Better Java Developer

My free newsletter keeps you ahead. Modern Java: new versions & features, performance, and JVM insights – once a month.

Search