I have had a look at the Java source code. It took me about 30 minutes to find the right file and analyse its behaviour.
The time zone detection for Linux is implemented in j2se/src/solaris/native/java/util/TimeZone_md.c
Java looks at the environment variable TZ first.
If it is not set, it reads /etc/sysconfig/clock.
If it does not find the file or a value for TZ in the file, it continues by examining /etc/localtime.
If /etc/localtime is a symbolic link, the timezone is derived from the destination of the link.
If /etc/localtime is a regular file, Java reads all the files in /usr/share/zoneinfo and tries to find a file that is identical to /etc/localtime. If a file is found, the time zone is derived from the path of the file.
I have found out that in my case, Europe/Amsterdam in /usr/share/zoneinfo is identical to /etc/localtime, and in addition there is a symbolic link called localtime pointing to /etc/localtime in /usr/share/zoneinfo. After deleting the symbolic link /usr/share/zoneinfo/localtime, Java detects the time zone correctly on all systems. So I guess whether or not this bug appears depends on the order in which Java cycles through the files in /usr/share/zoneinfo. If it finds the symbolic link to /etc/localtime first, the detection fails because the time zone cannot be derived from the path /usr/share/zoneinfo/localtime. If it finds the regular time zone file first, the detection succeeds.
Possible ways to solve this bug that I could imagine would be to remove /usr/share/zoneinfo/localtime (but it is probably needed by other applications/libraries), to get the files in /usr/share/zoneinfo in the correct order (bad) or implement a check in j2se/src/solaris/native/java/util/TimeZone_md.c if the file /usr/share/zoneinfo/localtime is a regular file and to skip all symbolic links.