EMT4J——Java版本迁移检测工具

EMT4J 是一个 Java 版本迁移兼容性检测工具,主要用于识别代码或依赖库在 Java 8→1111→17 升级过程中可能遇到的不兼容问题。它通过预定义规则(如 API 废弃、模块系统限制、JVM 参数变化等)扫描代码或 JAR 包,快速定位潜在风险,帮助开发者提前修复问题,确保迁移后应用正常运行。

  • 如下使用EMT4J扫描Jar包后的报告

EMT4J使用说明

使用说明

使用该工具检测之前,建议先把项目中的JDK版本、SpringBoot相关依赖及第三方依赖升级到对应的版本再使用该工具。如果直接使用该工具,会检测出很多低版本依赖的问题。

1、使用插件的方式运行

  • 将以下配置添加到 pom.xml 文件(如果是多模块项目,则添加到根 pom.xml 文件):
<build>
    <plugins>
        <plugin>
            <groupId>org.eclipse.emt4j</groupId>
            <artifactId>emt4j-maven-plugin</artifactId>
            <version>0.8.0-rules-internal</version>
            <executions>
                <execution>
                    <phase>process-test-classes</phase>
                    <goals>
                        <goal>check</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <fromVersion>8</fromVersion>
                <toVersion>17</toVersion>
                <outputFile>report.html</outputFile>
                <!--指定扫描的jar包-->
                <files>target/koca-admin-7.0.0-SNAPSHOT-full.jar</files>
            </configuration>
        </plugin>
    </plugins>
</build>
  • 然后运行以下命令:
mvn process-test-classes
  • EMT4J 的报告将在项目目录中生成。

2、直接在项目目录下运行以下命令,无需修改 pom.xml 文件:

前提条件,需要使用对应目标的JDK版本来执行,如执行的是8-17,需要使用17来执行该命令

# 以默认配置运行,默认检测的版本范围是 JDK 8 → JDK 11
mvn process-test-classes org.eclipse.emt4j:emt4j-maven-plugin:0.8.0-rules-internal:check
# 指定JDK8-17范围
mvn process-test-classes org.eclipse.emt4j:emt4j-maven-plugin:0.8.0-rules-internal:check -DfromVersion=8 -DtoVersion=17
# 通过 `-D` 指定输出文件和优先级
mvn process-test-classes org.eclipse.emt4j:emt4j-maven-plugin:0.8.0-rules-internal:check -DoutputFile=koca-admin-7.0.0-SNAPSHOT-full-report.html -Dpriority=p1

规则修改

去掉以下规则

1、从JDK 9,java version的schema发生了变化

从JDK 9开始,Java版本号的格式发生了变化:

旧格式(JDK 8及之前):1.x(例如 1.8.0_381)
新格式(JDK 9+):MAJOR.MINOR.SECURITY.PATCH(例如 9.0.1, 11.0.4, 17)

如:
位置: file:/D:/emt4j-0.8.0/admin/3-koca-admin-bootapp/target/koca-admin-6.4.0-SNAPSHOT-full.jar!/BOOT-INF/lib/byte-buddy-1.10.22.jar!/net/bytebuddy/ClassFileVersion$VersionLocator$ForLegacyVm.class, 目标: net.bytebuddy.ClassFileVersion$VersionLocator$ForLegacyVm

位于 byte-buddy-1.10.22.jar
问题:这个类使用了旧的版本号格式(1.x)来判断Java版本,在JDK 9+上会失效。
例如:if (System.getProperty("java.version").startsWith("1.8"))
→ 在JDK 9+中,java.version 返回 9.0.1,所以条件永远不成立。

大多都是一些三方包会被扫描到

2、JDK 11中时区数据为CLDR,java.text.DateFormat的使用默认的style进行format时在JDK 8和JDK 11输出不一致

DateFormat df = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT); 
System.out.println(df.format(new Date())); 
JDK 8的输出: Jan 14, 2021 11:17:37 AM ; JDK 11的输出: Jan 14, 2021, 11:16:45 AM 区别在于年份后面,对于JDK 8是空格,但是对于JDK 11是逗号.
JDK 8 JDK 11
Jan 14, 2021 11:17:37 AM Jan 14, 2021, 11:16:45 AM
年份后是空格 年份后是逗号
如:位置: file:/D:/emt4j-0.8.0/admin/3-koca-admin-bootapp/target/koca-admin-6.4.0-SNAPSHOT-full.jar!/BOOT-INF/lib/liquibase-core-4.8.0.jar!/liquibase/dbdoc/HTMLWriter.class, 目标: liquibase.dbdoc.HTMLWriter

修复:
1. 增加 "-Djava.locale.providers=COMPAT"到java选项,从而将默认时区与JDK 8的时区数据保持兼容 
2. 在使用DateFormat的时候避免使用默认的locale,而是显式指定Locale.

3、java.util.Calendar.getFirstDayOfWeek对于某些输入返回值在JDK 8和JDK 11不一致

Locale locale1 = Locale.forLanguageTag("nl"); 
System.out.println(new GregorianCalendar(locale1).getFirstDayOfWeek()); 
JDK 8的输出是2,但是JDK 11的输出是1
JDK 8 JDK 11
Locale.forLanguageTag("nl") → 星期一(返回 2) Locale.forLanguageTag("nl") → 星期日(返回 1)
荷兰语默认使用星期一为一周起始 CLDR 标准下荷兰语默认使用星期日
JDK 11 采用 CLDR 时区数据,而 JDK 8 用旧版数据。Locale 未指定区域时,JDK 11 会按 CLDR 规则处理。

位置: file:/D:/emt4j-0.8.0/admin/3-koca-admin-bootapp/target/koca-admin-6.4.0-SNAPSHOT-full.jar!/BOOT-INF/lib/logback-core-1.2.13.jar!/ch/qos/logback/core/util/TimeUtil.class, 目标: ch.qos.logback.core.util.TimeUtil

修复:
1. 增加 "-Djava.locale.providers=COMPAT"到java选项,从而将默认时区与JDK 8的时区数据保持兼容 
2. 在上面的场景中实例化Locale时,同时提供语言和区域,而不仅仅只提供语言

4、代码使用了JDK中标记为Deprecated的API

去掉了8-11、11-17中针对过期API的规则。这些只是被弃用了,还没有被删掉,扫描出来的太多了,大多都是一些低版本的三方包,把对应的三方包升级下就行

Deprecated的API后续可能会删除,存在潜在不兼容的风险

修复:参考标记为deprecated的API javadoc的说明替换为相应建议的API

全部规则

8-11 规则:

1. JDK internal API (JDK内部API)
  • 作用:检测使用 sun.* 等JDK内部API的情况
  • 优先级:p4
  • 说明:JDK 9+ 移除了内部API,使用会导致兼容性问题
2. System classloader not instance of URLClassLoader
  • 作用:检测系统类加载器是否仍是 URLClassLoader 的子类
  • 优先级:p1
  • 说明:JDK 9+ 系统类加载器不再是 URLClassLoader 的子类
3. Arrays.asList().toArray return type changed
  • 作用:检测 Arrays.asList().toArray() 返回类型变化
  • 优先级:p1
  • 说明:JDK 9+ 中返回类型从 Object[] 变为特定类型数组
4. Get java version
  • 作用:检测获取Java版本的代码
  • 优先级:p1
  • 说明:JDK 9+ 中Java版本获取方式有变化
5. JPMS require add-exports
  • 作用:检测需要添加 add-exports 的模块系统代码
  • 优先级:p3
  • 说明:JDK 9+ 引入模块系统,需要显式导出包
6. JPMS require add-opens
  • 作用:检测需要添加 add-opens 的模块系统代码
  • 优先级:p3
  • 说明:JDK 9+ 引入模块系统,需要显式打开包
7. DateFormat incompatible with CLDR
  • 作用:检测 DateFormat 在CLDR区域设置下的不兼容问题
  • 优先级:p3
  • 说明:JDK 11 使用 CLDR 时区数据,与 JDK 8 不兼容
8. NumberFormat incompatible with CLDR
  • 作用:检测 NumberFormat 在CLDR区域设置下的不兼容问题
  • 优先级:p3
  • 说明:JDK 11 使用 CLDR 时区数据,与 JDK 8 不兼容
9. Calendar#getFirstDayOfWeek incompatible with CLDR
  • 作用:检测 Calendar#getFirstDayOfWeek 在CLDR区域设置下的不兼容问题
  • 优先级:p3
  • 说明:JDK 11 使用 CLDR 时区数据,与 JDK 8 不兼容
10. java.util.regex.Pattern.compile flags check
  • 作用:检测 Pattern.compile() 在JDK 11中添加的标志检查
  • 优先级:p1
  • 说明:JDK 11 中 Pattern.compile() 添加了标志检查
11. JVM Option not compatible
  • 作用:检测不兼容的JVM选项
  • 优先级:p1
  • 说明:JDK 8 和 JDK 11 的JVM选项有变化
12. Remove CORBA
  • 作用:检测使用CORBA的代码
  • 优先级:p4
  • 说明:CORBA在JDK 11中已被移除
13. Removed API
  • 作用:检测已移除的API
  • 优先级:p1
  • 说明:JDK 11中移除了部分API
14. Incompatible Jar
  • 作用:检测不兼容的JAR包
  • 优先级:p1
  • 说明:某些JAR包在JDK 11中不兼容
15. Deprecated API
  • 作用:检测已废弃的API
  • 优先级:p4
  • 说明:JDK 11中废弃了部分API

11-17 规则:

1. Remove Nashorn
  • 作用:检测使用Nashorn的代码
  • 优先级:p3
  • 说明:Nashorn在JDK 11中已移除,在JDK 17中完全移除
2. Deprecate the Applet API for Removal
  • 作用:检测使用Applet API的代码
  • 优先级:p4
  • 说明:Applet API在JDK 11中已废弃,在JDK 17中将被移除
3. Remove RMI Activation
  • 作用:检测使用RMI Activation的代码
  • 优先级:p4
  • 说明:RMI Activation在JDK 11中已被移除
4. Cannot get security class’s field
  • 作用:检测无法获取安全类字段的代码
  • 优先级:p1
  • 说明:JDK 11+ 中安全类的字段访问被限制
5. JVM Option not compatible
  • 作用:检测不兼容的JVM选项
  • 优先级:p1
  • 说明:JDK 11 和 JDK 17 的JVM选项有变化
6. Incompatible Jar
  • 作用:检测不兼容的JAR包
  • 优先级:p1
  • 说明:某些JAR包在JDK 17中不兼容
7. Deprecated API
  • 作用:检测已废弃的API
  • 优先级:p4
  • 说明:JDK 17中废弃了部分API