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.
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.