Write a java code to detect the JVM version

I'm not sure what my score is, because it depends on what precisely you consider to be a lexical token, but I'm trying to abuse that counting system as much as possible with a long string...

It also depends on whether you count this as identifying 7 different versions or 16... (It could trivially be extended up to 190).

class V extends ClassLoader
{
    public static void main(String[]args)
    {
        for(byte b=60;;)
            try {
                byte[]buf="\u00ca\u00fe\u00ba\u00be\u0000\u0000\u00002\u0000\u0005\u0007\u0000\u0003\u0007\u0000\u0004\u0001\u0000\u0001A\u0001\u0000\u0010java/lang/Object\u0006\u0000\u0000\u0001\u0000\u0002\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000".getBytes("ISO-8859-1");
                buf[7]=b--;
                new V().defineClass(buf,0,53);
                System.out.println(b-43);
                break;
            }
            catch(Throwable t){}
    }
}

It works by attempting to define an interface in a custom classloader with descending major version numbers of the class format. The first one which doesn't throw a java.lang.UnsupportedClassVersionError corresponds to the VM's version.


6/102 = 0.0588

Detects 6 versions. Has 102 lexical tokens (down from 103, after I deleted public in public class).

import java.security.Signature;

class GuessVersion {
        public static void main(String[] args) {
                String version = "Java 1.1";
                try {
                        "".getBytes("ISO8859_13");
                        version = "Java 1.3";

                        "".getBytes("ISO8859_15");
                        version = "Java 1.4";

                        Signature.getInstance("SHA256withRSA");
                        version = "Java 5";

                        "".getBytes("KOI8_U");
                        version = "Java 6";

                        Signature.getInstance("SHA256withECDSA");
                        version = "Java 7";
                } catch(Exception e) {}
                System.out.println(version);
        }
}

Java 1.1 introduced character encodings and cryptographic algorithms to Java. Later versions added more encodings and algorithms. This program tries to use encodings and algorithms until it catches an exception. I expect a missing encoding to throw java.io.UnsupportedEncodingException and a missing algorithm to throw java.security.NoSuchAlgorithmException.

I had an old PowerPC Macintosh with four old versions of Java. My OpenBSD machine has two more versions, so I tested these six versions:

  • Java 1.1.8 in MRJ 2.2.6 for Mac OS 9.2.2
  • Java 1.3.1_16 for Mac OS X Panther
  • Java 1.4.2_21 for Mac OS X Tiger
  • Java 1.5.0_19 for Mac OS X Tiger
  • OpenJDK 1.6.0_32 for OpenBSD 5.5
  • OpenJDK 1.7.0_21 for OpenBSD 5.5

This program can also run in JamVM 1.5.4 and gcj 4.8.2 for OpenBSD, but does not identify them as different implementations. It only prints "Java 5".

Mac OS Runtime for Java

Thanks to "Write once, run everywhere!", I may write this program once, compile it once, and run one GuessVersion.class in all eight virtual machines. I need a compiler for Java 1.1, the oldest version in my collection.

My compiler is the javac tool from MRJ SDK 2.2. Because the Classic Mac OS had no command line, javac is a pretty graphical tool where I select files and options and click "Do Javac". After I edit my code, I just click "Do Javac" again.

javac from MRJ SDK 2.2 for Classic Mac OS

The easiest way to run GuessVersion.class is to open it in JBindery, another tool from MRJ SDK 2.2. The runtime is MRJ 2.2.6, an implementation of Java 1.1.8.


class Evil {
    public static void main(String... args) {
        String s1 = "Good";
        s1 += "morning";
        int a = 7;
        if (s1 != s1.intern())
            try {
                a--;
                javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar().equals(null);
            } catch (Throwable e) {
                a--;
            }
        System.out.println(a);
    }
}

The interning algorithm changed between Java 6 and 7. See https://stackoverflow.com/a/7224864/540552

XMLGregorianCalendar.equals(null) used to throw NullPointerException in java 5, but this was fixed in java 6. See http://bugs.sun.com/view_bug.do?bug_id=6285370

100 96 92 87 85 tokens here. Thanks to Peter Taylor for reducing 7 tokens.