Upgrading will tell you, with some testing. If you have a system of 800+ modules you should consider addressing your standardisation issues before you upgrade - try to use same versions of your dependencies - such as JDBC drivers, logging jars etc, and same for your developer build environments so everyone can make the same step together - and therefore rollback together if it fails at some stage.
This class will dump out versions of all .class and .jar files it sees in any directories or files it scans. It may help. Or not.
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-??";
}
}
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();
}
}