File#getCanonicalPath doesn't resolve Symlink in ancestors if file doesn't exist on Windows

XMLWordPrintable

    • windows

      ADDITIONAL SYSTEM INFORMATION :
      Linux
      Property settings:
          file.encoding = UTF-8
          file.separator = /
          java.class.path =
          java.class.version = 69.0
          java.home = /usr/lib/jvm/java-25-openjdk-amd64
          java.io.tmpdir = /tmp
          java.library.path = /usr/java/packages/lib
              /usr/lib/x86_64-linux-gnu/jni
              /lib/x86_64-linux-gnu
              /usr/lib/x86_64-linux-gnu
              /usr/lib/jni
              /lib
              /usr/lib
          java.runtime.name = OpenJDK Runtime Environment
          java.runtime.version = 25.0.2+10-Ubuntu-124.04
          java.specification.name = Java Platform API Specification
          java.specification.vendor = Oracle Corporation
          java.specification.version = 25
          java.vendor = Ubuntu
          java.vendor.url = https://ubuntu.com/
          java.vendor.url.bug = https://bugs.launchpad.net/ubuntu/+source/openjdk-25
          java.version = 25.0.2
          java.version.date = 2026-01-20
          java.vm.compressedOopsMode = Zero based
          java.vm.info = mixed mode, sharing
          java.vm.name = OpenJDK 64-Bit Server VM
          java.vm.specification.name = Java Virtual Machine Specification
          java.vm.specification.vendor = Oracle Corporation
          java.vm.specification.version = 25
          java.vm.vendor = Ubuntu
          java.vm.version = 25.0.2+10-Ubuntu-124.04
          jdk.debug = release
          line.separator = \n
          native.encoding = UTF-8
          os.arch = amd64
          os.name = Linux
          os.version = 6.17.0-14-generic
          path.separator = :
          stderr.encoding = UTF-8
          stdin.encoding = UTF-8
          stdout.encoding = UTF-8
          sun.arch.data.model = 64
          sun.boot.library.path = /usr/lib/jvm/java-25-openjdk-amd64/lib
          sun.cpu.endian = little
          sun.io.unicode.encoding = UnicodeLittle
          sun.java.launcher = SUN_STANDARD
          sun.jnu.encoding = UTF-8
          sun.management.compiler = HotSpot 64-Bit Tiered Compilers
          user.country = DE
          user.dir = /tmp/canonical-path-test
          user.home = /home/<username>
          user.language = de
          user.name = <username>

      openjdk version "25.0.2" 2026-01-20
      OpenJDK Runtime Environment (build 25.0.2+10-Ubuntu-124.04)
      OpenJDK 64-Bit Server VM (build 25.0.2+10-Ubuntu-124.04, mixed mode, sharing)

      Windows
      Property settings:
          file.encoding = UTF-8
          file.separator = \
          java.class.path =
          java.class.version = 69.0
          java.home = c:\Program Files\Eclipse Adoptium\jdk-25.0.2.10-hotspot
          java.io.tmpdir = C:\Users\bodewig\AppData\Local\Temp\
          java.library.path = c:\Program Files\Eclipse Adoptium\jdk-25.0.2.10-hotspot\bin
              C:\WINDOWS\Sun\Java\bin
              C:\WINDOWS\system32
              C:\WINDOWS
              C:\Program Files\Eclipse Adoptium\jdk-21.0.10.7-hotspot\bin
              C:\Program Files\Eclipse Adoptium\jdk-25.0.2.10-hotspot\bin
              C:\WINDOWS\system32
              C:\WINDOWS
              C:\WINDOWS\System32\Wbem
              C:\WINDOWS\System32\WindowsPowerShell\v1.0\
              C:\Program Files\dotnet\
              C:\WINDOWS\System32\OpenSSH\
              C:\Program Files\PowerShell\7\
              C:\Program Files\Git\cmd
              C:\Users\<username>\.dotnet\tools
              C:\Windows\Microsoft.NET\Framework\v4.0.30319
              C:\Users\<username>\AppData\Local\Microsoft\WindowsApps
              C:\Users\<username>\.dotnet\tools
              C:\xmlunit
              .
          java.runtime.name = OpenJDK Runtime Environment
          java.runtime.version = 25.0.2+10-LTS
          java.specification.name = Java Platform API Specification
          java.specification.vendor = Oracle Corporation
          java.specification.version = 25
          java.vendor = Eclipse Adoptium
          java.vendor.url = https://adoptium.net/
          java.vendor.url.bug = https://github.com/adoptium/adoptium-support/issues
          java.vendor.version = Temurin-25.0.2+10
          java.version = 25.0.2
          java.version.date = 2026-01-20
          java.vm.compressedOopsMode = Zero based
          java.vm.info = mixed mode, sharing
          java.vm.name = OpenJDK 64-Bit Server VM
          java.vm.specification.name = Java Virtual Machine Specification
          java.vm.specification.vendor = Oracle Corporation
          java.vm.specification.version = 25
          java.vm.vendor = Eclipse Adoptium
          java.vm.version = 25.0.2+10-LTS
          jdk.debug = release
          line.separator = \r \n
          native.encoding = Cp1252
          os.arch = amd64
          os.name = Windows 11
          os.version = 10.0
          path.separator = ;
          stderr.encoding = cp437
          stdin.encoding = cp437
          stdout.encoding = cp437
          sun.arch.data.model = 64
          sun.boot.library.path = c:\Program Files\Eclipse Adoptium\jdk-25.0.2.10-hotspot\bin
          sun.cpu.endian = little
          sun.cpu.isalist = amd64
          sun.io.unicode.encoding = UnicodeLittle
          sun.java.launcher = SUN_STANDARD
          sun.jnu.encoding = Cp1252
          sun.management.compiler = HotSpot 64-Bit Tiered Compilers
          sun.os.patch.level =
          user.country = US
          user.dir = C:\temp\canonical-path-test
          user.home = C:\Users\<username>
          user.language = en
          user.name = <username>
          user.script =
          user.variant =

      openjdk version "25.0.2" 2026-01-20 LTS
      OpenJDK Runtime Environment Temurin-25.0.2+10 (build 25.0.2+10-LTS)
      OpenJDK 64-Bit Server VM Temurin-25.0.2+10 (build 25.0.2+10-LTS, mixed mode, sharing)

      A DESCRIPTION OF THE PROBLEM :
      I'm a developer of Apache Ant and while working on improved symlink/junction support on windows stumbled over a difference of behavior in File.getCanonicalPath between Windows and Linux (and probably other Unices).

      Thanks to https://bugs.openjdk.org/browse/JDK-8003887 symlnks and junctions are resolved on Windows since Java 24. This works fine, as long as the file itself exists. If you ask for the canonical path of a non-existent file inside an existing directory symlinks in the file's ancestors are not resolved on Windows. They are resolved if the file exists. Linux resolves symlinks in ancestors in either case.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      On Linux:

      mkdir -p /tmp/canonical-path-test/normal
      cd /tmp/canonical-path-test
      touch normal/exists
      ln -s normal symlink
      # copy CanonicalPath.java here
      /usr/lib/jvm/java-25-openjdk-amd64/bin/javac CanonicalPath.java
      # run CanonicalPath for normal/exists, normal/does-not-exist, symlink/exists and symlink/does-not-exist respectively

      On Windows
      cd \temp
      mkdir canonical-path-test
      cd canonical-path-test
      # copy CanonicalPath.java here
      "c:\Program Files\Eclipse Adoptium\jdk-25.0.2.10-hotspot\bin\javac.exe" CanonicalPath.java
      mkdir normal
      mklink /D symlink c:\temp\canonical-path-test\normal
      symbolic link created for symlink <<===>> c:\temp\canonical-path-test\normal
      mklink /J junction c:\temp\canonical-path-test\normal
      Junction created for junction <<===>> c:\temp\canonical-path-test\normal
      echo > normal\exists
      # run CanonicalPath for normal/exists, normal/does-not-exist, symlink/exists, symlink/does-not-exist, junction/exists and junction/does-not-exist respectively


      ---------- BEGIN SOURCE ----------
      import java.io.*;

      public class CanonicalPath {

          public static void main(String[] args) throws IOException {
              if (args.length != 1) {
                  System.err.println("CanonicalPath file");
                  System.exit(1);
              }
              System.err.println("getCanonicalPath is " + new File(args[0]).getCanonicalPath());
          }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Unfortunately Path#toRealPath does not work at all if the path doesn't exist. The only workaround I came up with was walking up the ancestor tree until I find an existing directory. Invoke getCanonicalPath on that and then resolve the rest of the path relative to this resolved ancestor.

            Assignee:
            Brian Burkhalter
            Reporter:
            Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated: