14

Are there cases where File.getCanonicalPath() and File.toPath().toRealPath() will produce different results? They seem to do both rather similar things but the documentation never atually states that they are supposed to do the same thing. Are there border cases where I would to prefer one method over the other? And how about File.getAbsolutePath() versus Path.toAbsolutePath() - are they supposed to work in the same way?

jpp1
  • 2,019
  • 3
  • 22
  • 43

5 Answers5

13

Conclusions:

  • getAbsolutePath and getPath never fail as they don't do validation
  • getCanonicalPath reach invalid results when drive letter from url is invalid or different than the current folder
  • toPath().toRealPath() is checking the validity but the file needs to exist and can also follow or not follow symbolic links
  • toPath() is safe enough and doesn't need the file to exist.
  • .toPath().toAbsolutePath().normalize() is the best one without the need for file to exist

I did a similar test of @John in windows

  @Test
  public void testCanonical() throws IOException {
    test("d:tarGet\\..\\Target", "File exist and drive letter is on the current one");
    test("d:tarGet\\..\\Target\\.\\..\\", "File exist and drive letter is on the current one, but parent of current drive should exist");
    test("d:tarGet\\non-existent\\..\\..\\Target\\.\\..\\", "Relative path contains non-existent file");
    test("d:target\\\\file", "Double slash");
    test("c:tarGet\\..\\Target\\.", "File doesn't exist and drive letter is on different drive than the current one");
    test("l:tarGet\\..\\Target\\.\\..\\", "Drive letter doesn't exist");
    test("za:tarGet\\..\\Target\\.\\..\\", "Drive letter is double so not valid");
    test("d:tarGet|Suffix", "Path contains invalid chars in windows (|)");
    test("d:tarGet\u0000Suffix", "Path contains invalid chars in both linux and windows (\\0)");
  }

  private void test(String filename, String message) throws IOException {
    java.io.File file = new java.io.File(filename);
    System.out.println("Use:  " + filename + " -> " + message);
    System.out.println("F-GET:     " + Try.of(() -> file.getPath()));
    System.out.println("F-ABS:     " + Try.of(() -> file.getAbsolutePath()));
    System.out.println("F-CAN:     " + Try.of(() -> file.getCanonicalPath()));
    System.out.println("P-TO:      " + Try.of(() -> file.toPath()));
    System.out.println("P-ABS:     " + Try.of(() -> file.toPath().toAbsolutePath()));
    System.out.println("P-NOR:     " + Try.of(() -> file.toPath().normalize()));
    System.out.println("P-NOR-ABS: " + Try.of(() -> file.toPath().normalize().toAbsolutePath()));
    System.out.println("P-ABS-NOR: " + Try.of(() -> file.toPath().toAbsolutePath().normalize()));
    System.out.println("P-REAL:    " + Try.of(() -> file.toPath().toRealPath()));
    System.out.println("");
  }

The results are:

Use:  d:tarGet\..\Target -> File exist and drive letter is on the current one
F-GET:     Success(d:tarGet\..\Target)
F-ABS:     Success(d:\home\raiser\work\restfs\tarGet\..\Target)
F-CAN:     Success(D:\home\raiser\work\restfs\target)
P-TO:      Success(d:tarGet\..\Target)
P-ABS:     Success(D:\home\raiser\work\restfs\tarGet\..\Target)
P-NOR:     Success(d:Target)
P-NOR-ABS: Success(D:\home\raiser\work\restfs\Target)
P-ABS-NOR: Success(D:\home\raiser\work\restfs\Target)
P-REAL:    Success(D:\home\raiser\work\restfs\target)

Use:  d:tarGet\..\Target\.\..\ -> File exist and drive letter is on the current one, but parent of current drive should exist
F-GET:     Success(d:tarGet\..\Target\.\..)
F-ABS:     Success(d:\home\raiser\work\restfs\tarGet\..\Target\.\..)
F-CAN:     Success(D:\home\raiser\work\restfs)
P-TO:      Success(d:tarGet\..\Target\.\..)
P-ABS:     Success(D:\home\raiser\work\restfs\tarGet\..\Target\.\..)
P-NOR:     Success(d:)
P-NOR-ABS: Success(D:\home\raiser\work\restfs\)
P-ABS-NOR: Success(D:\home\raiser\work\restfs)
P-REAL:    Success(D:\home\raiser\work\restfs)

Use:  d:tarGet\non-existent\..\..\Target\.\..\ -> Relative path contains non-existent file
F-GET:     Success(d:tarGet\non-existent\..\..\Target\.\..)
F-ABS:     Success(d:\home\raiser\work\restfs\tarGet\non-existent\..\..\Target\.\..)
F-CAN:     Success(D:\home\raiser\work\restfs)
P-TO:      Success(d:tarGet\non-existent\..\..\Target\.\..)
P-ABS:     Success(D:\home\raiser\work\restfs\tarGet\non-existent\..\..\Target\.\..)
P-NOR:     Success(d:)
P-NOR-ABS: Success(D:\home\raiser\work\restfs\)
P-ABS-NOR: Success(D:\home\raiser\work\restfs)
P-REAL:    Success(D:\home\raiser\work\restfs)

Use:  d:target\\file -> Double slash
F-GET:     Success(d:target\file)
F-ABS:     Success(d:\home\raiser\work\restfs\target\file)
F-CAN:     Success(D:\home\raiser\work\restfs\target\file)
P-TO:      Success(d:target\file)
P-ABS:     Success(D:\home\raiser\work\restfs\target\file)
P-NOR:     Success(d:target\file)
P-NOR-ABS: Success(D:\home\raiser\work\restfs\target\file)
P-ABS-NOR: Success(D:\home\raiser\work\restfs\target\file)
P-REAL:    Failure(java.nio.file.NoSuchFileException: D:\home\raiser\work\restfs\target\file)

Use:  c:tarGet\..\Target\. -> File doesn't exist and drive letter is on different drive than the current one
F-GET:     Success(c:tarGet\..\Target\.)
F-ABS:     Success(c:\\tarGet\..\Target\.)
F-CAN:     Success(C:\Target)
P-TO:      Success(c:tarGet\..\Target\.)
P-ABS:     Success(C:\tarGet\..\Target\.)
P-NOR:     Success(c:Target)
P-NOR-ABS: Success(C:\Target)
P-ABS-NOR: Success(C:\Target)
P-REAL:    Failure(java.nio.file.NoSuchFileException: C:\Target)

Use:  l:tarGet\..\Target\.\..\ -> Drive letter doesn't exist
F-GET:     Success(l:tarGet\..\Target\.\..)
F-ABS:     Success(l:\tarGet\..\Target\.\..)
F-CAN:     Success(L:\)
P-TO:      Success(l:tarGet\..\Target\.\..)
P-ABS:     Failure(java.io.IOError: java.io.IOException: Unable to get working directory of drive 'L')
P-NOR:     Success(l:)
P-NOR-ABS: Failure(java.io.IOError: java.io.IOException: Unable to get working directory of drive 'L')
P-ABS-NOR: Failure(java.io.IOError: java.io.IOException: Unable to get working directory of drive 'L')
P-REAL:    Failure(java.io.IOException: Unable to get working directory of drive 'L')

Use:  za:tarGet\..\Target\.\..\ -> Drive letter is double so not valid
F-GET:     Success(za:tarGet\..\Target\.\..)
F-ABS:     Success(D:\home\raiser\work\restfs\za:tarGet\..\Target\.\..)
F-CAN:     Success(D:\home\raiser\work\restfs)
P-TO:      Failure(java.nio.file.InvalidPathException: Illegal char <:> at index 2: za:tarGet\..\Target\.\..)
P-ABS:     Failure(java.nio.file.InvalidPathException: Illegal char <:> at index 2: za:tarGet\..\Target\.\..)
P-NOR:     Failure(java.nio.file.InvalidPathException: Illegal char <:> at index 2: za:tarGet\..\Target\.\..)
P-NOR-ABS: Failure(java.nio.file.InvalidPathException: Illegal char <:> at index 2: za:tarGet\..\Target\.\..)
P-ABS-NOR: Failure(java.nio.file.InvalidPathException: Illegal char <:> at index 2: za:tarGet\..\Target\.\..)
P-REAL:    Failure(java.nio.file.InvalidPathException: Illegal char <:> at index 2: za:tarGet\..\Target\.\..)

Use:  d:tarGet|Suffix -> Path contains invalid chars in windows (|)
F-GET:     Success(d:tarGet|Suffix)
F-ABS:     Success(d:\home\raiser\work\restfs\tarGet|Suffix)
F-CAN:     Failure(java.io.IOException: The filename, directory name, or volume label syntax is incorrect)
P-TO:      Failure(java.nio.file.InvalidPathException: Illegal char <|> at index 8: d:tarGet|Suffix)
P-ABS:     Failure(java.nio.file.InvalidPathException: Illegal char <|> at index 8: d:tarGet|Suffix)
P-NOR:     Failure(java.nio.file.InvalidPathException: Illegal char <|> at index 8: d:tarGet|Suffix)
P-NOR-ABS: Failure(java.nio.file.InvalidPathException: Illegal char <|> at index 8: d:tarGet|Suffix)
P-ABS-NOR: Failure(java.nio.file.InvalidPathException: Illegal char <|> at index 8: d:tarGet|Suffix)
P-REAL:    Failure(java.nio.file.InvalidPathException: Illegal char <|> at index 8: d:tarGet|Suffix)
raisercostin
  • 8,777
  • 5
  • 67
  • 76
3

A canonical path is absolute and unique, but will have different meaning on different systems.

A canonical pathname is both absolute and unique. The precise definition of canonical form is system-dependent.

A real path is the actual path with respect to the system. You would also have to pass in whether or not you don't deal with symbolic links, where it's implicitly handled with canonicalPath.

The precise definition of this method is implementation dependent but in general it derives from this path, an absolute path that locates the same file as this path, but with name elements that represent the actual name of the directories and the file. For example, where filename comparisons on a file system are case insensitive then the name elements represent the names in their actual case. Additionally, the resulting path has redundant name elements removed.

So yes, these two methods can return different things, but it really depends on your system. If you need something that's unique, then canonicalPath is your safest bet, even if it's not a Path.

Makoto
  • 104,088
  • 27
  • 192
  • 230
  • 1
    Yes, thanks, but those two definitions essentially both say: we will do something to your file/path to make it "real" or "canonical" but we do not really tell you exactly what and we do not even tell you if we do the same thing in both methods (assuming for the moment that we use toRealPath() with symbolic link expansion). I find that frustrating and not very professional. The documentation does not even say if the way how this works depends on just on the OS or file system or may also depend on the Java implementation. – jpp1 Jun 23 '15 at 07:55
2

What I noticed from my tests is that

  • Path.toRealPath() will throw java.nio.file.NoSuchFileException if the file does not exist (Javadoc: Returns the real path of an existing file.)

  • File.getCanonicalPath() will not throw an exception if the file does not exist (it will only throw IOException if the file name itself is invalid and contains a '\0' char in it).

So the former would not be suitable if you want to use it for path checks before actually creating the file.

You are right that the Javadoc for both methods is somewhat shallow.

anre
  • 3,617
  • 26
  • 33
1

Sure, the example below shows some of the differences. Also getCanonicalPath will throw an exception if the file does not exist.

getCanonicalPath returns the path in its canonical or simplest form (from http://www.merriam-webster.com/dictionary/canonical%20form)

import java.io.File;

public class FileExample {

    public static void main(String[] args) throws Exception {
            File file = new File("/TEMP/../TEMP/myfile.txt");
            System.out.println("ABS: " + file.getAbsolutePath());
            System.out.println(" TO: " + file.toPath());
            System.out.println("GET: " + file.getPath());
            System.out.println("CAN: " + file.getCanonicalPath());
        }
    }


ABS: C:\TEMP\..\TEMP\myfile.txt
 TO: \TEMP\..\TEMP\myfile.txt
GET: \TEMP\..\TEMP\myfile.txt
CAN: C:\TEMP\myfile.txt
John
  • 3,458
  • 4
  • 33
  • 54
  • I was innterested in Path.toRealPath() which seems to do something very similar to File.getCanonicalPath() – jpp1 Jun 23 '15 at 07:50
  • 1
    It's not true, that the file must exist for `File#getCanonicalPath()`. That is only true for `Path#getRealPath()`. The answer of [anre](https://stackoverflow.com/a/36842903/572645) is correct instead. – radlan Dec 09 '19 at 08:29
1

The API states that the canonical path usually removes redundancies and resolves symbolic links and so on.

Try the following on a UNIX machine:

File file = new File("../test.txt"); // execute from /tmp/java/example
file.getAbsolutePath();  // evaluates to /tmp/java/example/../test.txt
file.getCanonicalPath(); // evaluates to /tmp/java/test.txt

The difference between File and Path is that Path is part of the newer NIO API which has many improvements and is more flexible.

As an example you could exchange the implementation of the file system with NIO (see https://github.com/google/jimfs), whereas java.io.File forces you to operate on your host file system.

Franz Becker
  • 705
  • 5
  • 8
  • I knew that my question was about Path.toRealPath() versus File.getCanonicalPath() because they seem to do very similar things. – jpp1 Jun 23 '15 at 07:51