128

Do any of you know of a tool that will search for .class files and then display their compiled versions?

I know you can look at them individually in a hex editor but I have a lot of class files to look over (something in my giant application is compiling to Java6 for some reason).

1

12 Answers 12

153

Use the javap tool that comes with the JDK. The -verbose option will print the version number of the class file.

> javap -verbose MyClass
Compiled from "MyClass.java"
public class MyClass
  SourceFile: "MyClass.java"
  minor version: 0
  major version: 46
...

To only show the version:

WINDOWS> javap -verbose MyClass | find "version"
LINUX  > javap -verbose MyClass | grep version
2
  • 5
    Version major.minor=JDK/JavaSE; 45.3=JDK1.1; 46.0=JDK1.2; 47.0=JDK1.3; 48.0=JDK1.4; 49.0=JavaSE5(1.5); 51.0=JavaSE7(1.7); 50.0=JavaSE6(1.6); 52.0=JavaSE8(1.8); 53.0=JavaSE9; 54.0=JavaSE10; 55.0=JavaSE11; 56.0=JavaSE12; 57.0=JavaSE13; 58.0=JavaSE14;
    – nephewtom
    Commented Jul 24, 2019 at 14:50
  • od is 3 orders of magnitude faster than javap so for scripting use od. stackoverflow.com/a/25201428/1133275 Commented Oct 31, 2021 at 8:36
50

It is easy enough to read the class file signature and get these values without a 3rd party API. All you need to do is read the first 8 bytes.

ClassFile {
    u4 magic;
    u2 minor_version;
    u2 major_version;

For class file version 51.0 (Java 7), the opening bytes are:

CA FE BA BE 00 00 00 33

...where 0xCAFEBABE are the magic bytes, 0x0000 is the minor version and 0x0033 is the major version.

import java.io.*;

public class Demo {
  public static void main(String[] args) throws IOException {
    ClassLoader loader = Demo.class.getClassLoader();
    try (InputStream in = loader.getResourceAsStream("Demo.class");
        DataInputStream data = new DataInputStream(in)) {
      if (0xCAFEBABE != data.readInt()) {
        throw new IOException("invalid header");
      }
      int minor = data.readUnsignedShort();
      int major = data.readUnsignedShort();
      System.out.println(major + "." + minor);
    }
  }
}

Walking directories (File) and archives (JarFile) looking for class files is trivial.

Oracle's Joe Darcy's blog lists the class version to JDK version mappings up to Java 7:

Target   Major.minor Hex
1.1      45.3        0x2D
1.2      46.0        0x2E
1.3      47.0        0x2F
1.4      48.0        0x30
5 (1.5)  49.0        0x31
6 (1.6)  50.0        0x32
7 (1.7)  51.0        0x33
8 (1.8)  52.0        0x34
9        53.0        0x35
1
  • Also remember that assert is only run if it's enabled when launching java so you may read junk files if you're not using IllegalArgumentException (for example)
    – jontejj
    Commented Jul 4, 2013 at 15:38
25

On Unix-like

file /path/to/Thing.class

Will give the file type and version as well. Here is what the output looks like:

compiled Java class data, version 49.0

3
  • (simplified from WMR's answer)
    – phunehehe
    Commented Jul 7, 2011 at 10:34
  • this is way simpler than the other solutions
    – mmuller
    Commented Jul 12, 2017 at 15:23
  • I was going to comment my answer: od --endian=big -An -j6 -N2 -t u2 MyFile.class (to read 2 bytes, number 7 and 8, after skipping the first 6 and displaying it as big-endian decimal), but your answer is much more elegant, and includes minor version, if available, and it's nearly as fast. Commented Dec 20, 2023 at 6:25
9

If you are on a unix system you could just do a

find /target-folder -name \*.class | xargs file | grep "version 50\.0"

(my version of file says "compiled Java class data, version 50.0" for java6 classes).

1
  • On macOS (10.12.6 at least), the output is even more helpful: file *.class produces: ClassName.class: compiled Java class data, version 50.0 (Java 1.6)
    – Gary
    Commented Aug 19, 2017 at 0:16
6

Yet another java version check

od -t d -j 7 -N 1 ApplicationContextProvider.class | head -1 | awk '{print "Java", $2 - 44}'
6

In eclipse if you don't have sources attached. Mind the first line after the attach source button.

// Compiled from CDestinoLog.java (version 1.5 : 49.0, super bit)

enter image description here

4

Read the 8th byte to decimal:

Unix-like: hexdump -s 7 -n 1 -e '"%d"' Main.class

Windows: busybox.exe hexdump -s 7 -n 1 -e '"%d"' Main.class

Output example:

55

Explain:

  • -s 7 Offset 7
  • -n 1 Limit 1
  • -e '"%d"' Print as decimal

Version map:

JDK 1.1 = 45 (0x2D hex)
JDK 1.2 = 46 (0x2E hex)
JDK 1.3 = 47 (0x2F hex)
JDK 1.4 = 48 (0x30 hex)
Java SE 5.0 = 49 (0x31 hex)
Java SE 6.0 = 50 (0x32 hex)
Java SE 7 = 51 (0x33 hex)
Java SE 8 = 52 (0x34 hex)
Java SE 9 = 53 (0x35 hex)
Java SE 10 = 54 (0x36 hex)
Java SE 11 = 55 (0x37 hex)
Java SE 12 = 56 (0x38 hex)
Java SE 13 = 57 (0x39 hex)
Java SE 14 = 58 (0x3A hex)
Java SE 15 = 59 (0x3B hex)
Java SE 16 = 60 (0x3C hex)
Java SE 17 = 61 (0x3D hex)
3

Maybe this helps somebody, too. Looks there is more easy way to get JAVA version used to compile/build .class. This way is useful to application/class self check on JAVA version.

I have gone through JDK library and found this useful constant: com.sun.deploy.config.BuiltInProperties.CURRENT_VERSION. I do not know since when it is in JAVA JDK.

Trying this piece of code for several version constants I get result below:

src:

System.out.println("JAVA DEV       ver.: " + com.sun.deploy.config.BuiltInProperties.CURRENT_VERSION);
System.out.println("JAVA RUN     v. X.Y: " + System.getProperty("java.specification.version") );
System.out.println("JAVA RUN v. W.X.Y.Z: " + com.sun.deploy.config.Config.getJavaVersion() ); //_javaVersionProperty
System.out.println("JAVA RUN  full ver.: " + System.getProperty("java.runtime.version")  + " (may return unknown)" );
System.out.println("JAVA RUN       type: " + com.sun.deploy.config.Config.getJavaRuntimeNameProperty() );

output:

JAVA DEV       ver.: 1.8.0_77
JAVA RUN     v. X.Y: 1.8
JAVA RUN v. W.X.Y.Z: 1.8.0_91
JAVA RUN  full ver.: 1.8.0_91-b14 (may return unknown)
JAVA RUN       type: Java(TM) SE Runtime Environment

In class bytecode there is really stored constant - see red marked part of Main.call - constant stored in .class bytecode

Constant is in class used for checking if JAVA version is out of date (see How Java checks that is out of date)...

3

A java-based solution using version magic numbers. Below it is used by the program itself to detect its bytecode version.

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.IOUtils;
public class Main {
    public static void main(String[] args) throws DecoderException, IOException {
        Class clazz = Main.class;
        Map<String,String> versionMapping = new HashMap();
        versionMapping.put("002D","1.1");
        versionMapping.put("002E","1.2");
        versionMapping.put("002F","1.3");
        versionMapping.put("0030","1.4");
        versionMapping.put("0031","5.0");
        versionMapping.put("0032","6.0");
        versionMapping.put("0033","7");
        versionMapping.put("0034","8");
        versionMapping.put("0035","9");
        versionMapping.put("0036","10");
        versionMapping.put("0037","11");
        versionMapping.put("0038","12");
        versionMapping.put("0039","13");
        versionMapping.put("003A","14");
        
        InputStream stream = clazz.getClassLoader()
            .getResourceAsStream(clazz.getName().replace(".", "/") + ".class");
        byte[] classBytes = IOUtils.toByteArray(stream);
        String versionInHexString = 
            Hex.encodeHexString(new byte[]{classBytes[6],classBytes[7]});
        System.out.println("bytecode version: "+versionMapping.get(versionInHexString));
    }
}
1

Just another reference that can also check if a class was compiled with preview features:

    public final class JavaClassVersion {
        private final int major;
        private final int minor;

        private JavaClassVersion(int major, int minor) {
            this.major = major;
            this.minor = minor;
        }

        public int major() {
            return major;
        }

        public int minor() {
            return minor;
        }

        public static JavaClassVersion of(Path artifactPath) {
            try (InputStream in = Files.newInputStream(artifactPath);
                    DataInputStream data = new DataInputStream(in)) {
                if (0xCAFEBABE != data.readInt()) {
                    throw new IOException("invalid header");
                }
                int minor = data.readUnsignedShort();
                int major = data.readUnsignedShort();
                return new JavaClassVersion(major, minor);
            } catch (IOException ex) {
                throw new UncheckedIOException(ex);
            }
        }

        String getJavaVersion() {
            switch (major) {
                case 50:
                    return "1.6";
                case 51:
                    return "1.7";
                case 52:
                    return "1.8";
                case 53:
                    return "9";
                case 54:
                    return "10";
                case 55:
                    return "11";
                case 56:
                    return "12";
                case 57:
                    return "13";
                case 58:
                    return "14";
                case 59:
                    return "15";
                case 60:
                    return "16";
                case 61:
                    return "17";
                case 62:
                    return "18";
                case 63:
                    return "19";
                case 64:
                    return "20";
                case 65:
                    return "21";
                default:
                    return "";
            }
        }

        boolean isPreview() {
            return minor == 65535;
        }
    }
0

The simplest way is to scan a class file using many of the answers here which read the class file magic bytes.

However some code is packaged in jars or other archive formats like WAR and EAR, some of which contain other archives or class files, plus you now have multi-release JAR files - see JEP-238 which use different JDK compilers per JAR.

This program scans classes from a list of files + folders and prints summary of java class file versions for each component including each JAR within WAR/EARs:

public static void main(String[] args) throws IOException {
    var files = Arrays.stream(args).map(Path::of).collect(Collectors.toList());
    ShowClassVersions v = new ShowClassVersions();
    for (var f : files) {
        v.scan(f);
    }
    v.print();
}

Example output from a scan:

Version: 49.0 ~ JDK-5
   C:\jars\junit-platform-console-standalone-1.7.1.jar
Version: 50.0 ~ JDK-6
   C:\jars\junit-platform-console-standalone-1.7.1.jar
Version: 52.0 ~ JDK-8
   C:\java\apache-tomcat-10.0.12\lib\catalina.jar
   C:\jars\junit-platform-console-standalone-1.7.1.jar
Version: 53.0 ~ JDK-9
   C:\java\apache-tomcat-10.0.12\lib\catalina.jar
   C:\jars\junit-platform-console-standalone-1.7.1.jar

The scanner:

public class ShowClassVersions {
    private TreeMap<String, ArrayList<String>> vers = new TreeMap<>();
    private static final byte[] CLASS_MAGIC = new byte[] { (byte) 0xca, (byte) 0xfe, (byte) 0xba, (byte) 0xbe };
    private final byte[] bytes = new byte[8];

    private String versionOfClass(InputStream in) throws IOException  {
        int c = in.readNBytes(bytes, 0, bytes.length);
        if (c == bytes.length && Arrays.mismatch(bytes, CLASS_MAGIC) == CLASS_MAGIC.length) {
            int minorVersion = (bytes[4] << 8) + (bytes[4] << 0);
            int majorVersion = (bytes[6] << 8) + (bytes[7] << 0);
            return ""+ majorVersion + "." + minorVersion;
        }
        return "Unknown";
    }

    private Matcher classes = Pattern.compile("\\.(class|ear|war|jar)$").matcher("");

    // This code scans any path (dir or file):
    public void scan(Path f) throws IOException {
        try (var stream = Files.find(f, Integer.MAX_VALUE,
                (p, a) -> a.isRegularFile() && classes.reset(p.toString()).find())) {
            stream.forEach(this::scanFile);
        }
    }

    private void scanFile(Path f) {
        String fn = f.getFileName().toString();
        try {
            if (fn.endsWith(".ear") || fn.endsWith(".war") || fn.endsWith(".jar"))
                scanArchive(f);
            else if (fn.endsWith(".class"))
                store(f.toAbsolutePath().toString(), versionOfClass(f));
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    private void scanArchive(Path p) throws IOException {
        try (InputStream in = Files.newInputStream(p)) {
            scanArchive(p.toAbsolutePath().toString(), Files.newInputStream(p));
        }
    }

    private void scanArchive(String desc, InputStream in) throws IOException {
        HashSet<String> versions = new HashSet<>();
        ZipInputStream zip = new ZipInputStream(in);
        for (ZipEntry entry = null; (entry = zip.getNextEntry()) != null; ) {
            String name = entry.getName();
            // There could be different compiler versions per class in one jar
            if (name.endsWith(".class")) {
                versions.add(versionOfClass(zip));
            } else if (name.endsWith(".jar") || name.endsWith(".war")) {
                scanArchive(desc + " => " + name, zip);
            }
        }
        if (versions.size() > 1)
            System.out.println("Warn: "+desc+" contains multiple versions: "+versions);

        for (String version : versions)
            store(desc, version);
    }

    private String versionOfClass(Path p) throws IOException {
        try (InputStream in = Files.newInputStream(p)) {
            return versionOfClass(in);
        }
    }

    private void store(String path, String jdkVer) {
        vers.computeIfAbsent(jdkVer, k -> new ArrayList<>()).add(path);
    }

    // Could add a mapping table for JDK names, this guesses based on (JDK17 = 61.0)
    public void print() {
        for (var ver : vers.keySet()) {
            System.out.println("Version: " + ver + " ~ " +jdkOf(ver));
            for (var p : vers.get(ver)) {
                System.out.println("   " + p);
            }
        }
    }

    private static String jdkOf(String ver)  {
        try {
            return "JDK-"+((int)Float.parseFloat(ver)-44);
        }
        catch(NumberFormatException nfe)
        {
            return "JDK-??";
        }
    }
}
0

I did my own version with bash here, when using the command file it will show the version it was compiled with (but it's much faster), otherwise it will show both the minor and the major version

#/bin/bash
# From a base path get the class version for all the class files
#To extract all the class files from the jars
#find ./ -name "*.jar" -exec jar -xf {} \;

BASE=$1
for FILE in $(find "$BASE" -iname "*.class") ; do
    #file $FILE # Only compiler version
    echo -e "$FILE: "
    javap -verbose $FILE | grep version | grep version | tr '\n' ' ' # Minor and major version
done

Not the answer you're looking for? Browse other questions tagged or ask your own question.