Skip to content

Infer JDK version from module-info.class #900

@algomaster99

Description

@algomaster99

Background

Java Platform Module System was introduced in JDK 9 and it allowed to include module-info.java at the root of each module. The major benefits it offers is it limits access to internal APIs. You can always access them using reflection. But modules require you to explicitly allow that.

module-info.class classfile format

One fundamental module is java.base which contains all the core APIs and is implicitly required by all Java modules. Even an empty module.info.java:

module com.example.hello { }

is compiled to

  Last modified Oct 15, 2025; size 161 bytes
  SHA-256 checksum 21203d6840fc24d7143f5ff84e8b9d0291006163892e7168090fd2874faf32dd
  Compiled from "module-info.java"
module com.example.hello
  minor version: 0
  major version: 69
  flags: (0x8000) ACC_MODULE
  this_class: #1                          // module-info
  super_class: #0
  interfaces: 0, fields: 0, methods: 0, attributes: 2
Constant pool:
   #1 = Class              #2             // "module-info"
   #2 = Utf8               module-info
   #3 = Utf8               SourceFile
   #4 = Utf8               module-info.java
   #5 = Utf8               Module
   #6 = Unknown            #7             // "com.example.hello"
   #7 = Utf8               com.example.hello
   #8 = Unknown            #9             // "java.base"
   #9 = Utf8               java.base
  #10 = Utf8               25
{
}
SourceFile: "module-info.java"
Module:
  #6,0                                    // "com.example.hello"
  #0
  1                                       // requires
    #8,8000                                 // "java.base" ACC_MANDATED
    #10                                     // 25
  0                                       // exports
  0                                       // opens
  0                                       // uses
  0                                       // provides

1 module is required which is java.base

Based on the Java 11 spec, requires will have three types of information

  1. requires_index - index into the constant pool UTF8 entry which is the name of the module; #8 Java.base in the above example
  2. requires_flags - #8000 in our case which means it was implicitly declared
  3. requires_version_index - version information about the current module

Where to infer from?

We are interested to look for requires_version_index in module-info.class. If the Java application is modularized (module-info.class) is present, we will most likely have version information.

I am not sure what version you will get if you compile with jlink.

Example from experiment

While reproducing maven,jakarta.el:jakarta.el-api,6.0.1,jakarta.el-api-6.0.1.jar, we get the following difference:

│ ├── javap -verbose -constants -s -l -private {}
│ │ @@ -1,8 +1,8 @@
│ │ -  SHA-256 checksum 295a27ff84922d360fbd3260f469bce75a6d5c89bc1103eaa5cf6abb5fdb07d0
│ │ +  SHA-256 checksum 696e2c2cf9be72a87fbb95acc90839eca4d65ba9f69adcee009454dd0059ffd2
│ │    Compiled from "module-info.java"
│ │  module jakarta.el@6.0.1
│ │    minor version: 0
│ │    major version: 61
│ │    flags: (0x8000) ACC_MODULE
│ │    this_class: #2                          // "module-info"
│ │    super_class: #0
│ │ @@ -14,15 +14,15 @@
│ │     #4 = Utf8               jakarta.el
│ │     #5 = Module             #4             // "jakarta.el"
│ │     #6 = Utf8               6.0.1
│ │     #7 = Utf8               jakarta/el
│ │     #8 = Package            #7             // jakarta/el
│ │     #9 = Utf8               java.base
│ │    #10 = Module             #9             // "java.base"
│ │ -  #11 = Utf8               17.0.16
│ │ +  #11 = Utf8               17.0.2

We compiled with 17.0.6 but the upstream was compiled with 17.0.2. Note that this wasn't the only difference, there was also difference as a result of https://bugs.openjdk.org/browse/JDK-8273914, but I will explain that in another issue and relink here.

We have 11 instances out of 500 where there was a content mismatch. However, personally, I have not seen many projects leverage the module system of Java so there is a trade-off here.

11 instances
maven,com.sun.xml.ws:jaxws-rt,4.0.3,jaxws-rt-4.0.3.jar
maven,com.zaxxer:HikariCP,5.1.0,HikariCP-5.1.0.jar
maven,jakarta.el:jakarta.el-api,6.0.1,jakarta.el-api-6.0.1.jar
maven,com.sun.xml.messaging.saaj:saaj-impl,3.0.4,saaj-impl-3.0.4.jar
maven,io.smallrye.reactive:mutiny,2.6.2,mutiny-2.6.2.jar
maven,org.roaringbitmap:RoaringBitmap,1.2.0,RoaringBitmap-1.2.0.jar
maven,org.snakeyaml:snakeyaml-engine,2.9,snakeyaml-engine-2.9.jar
maven,org.eclipse.angus:angus-activation,2.0.2,angus-activation-2.0.2.jar
maven,jakarta.xml.soap:jakarta.xml.soap-api,3.0.2,jakarta.xml.soap-api-3.0.2.jar
maven,org.glassfish.jaxb:jaxb-runtime,4.0.5,jaxb-runtime-4.0.5.jar
maven,jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api,3.0.2,jakarta.servlet.jsp.jstl-api-3.0.2.jar

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions