Maven 的 source、target、release:字节码兼容不等于 API 兼容 target已经设成8javap也显示major version: 52为什么代码里还能调用 Java 11 才提供的String.isBlank()因为类文件格式和类库 API 是两项检查。先把这个反例编译出来再回到 Spring Boot 的 POM 看配置几个属性的作用就清楚了。先看一个能通过编译的反例保存为src/ApiTrap.javapublicclassApiTrap{publicstaticvoidmain(String[]args){System.out.println( .isBlank());}}本次使用 OpenJDK21.0.213-58。在命令行执行javac-encodingUTF-8-source8-target8-dout/source-target src/ApiTrap.java javap-verbose-classpathout/source-target ApiTrap编译成功但伴随选项和引导类路径相关警告。javap的结果同时包含major version: 52 java/lang/String.isBlank:()Z52对应 Java 8 的类文件格式后面那条方法引用却指向String.isBlank()。编译器当前能看到 JDK 21 的类库因此找得到这个方法。-source 8限制语言规则-target 8指定输出格式它们没有自动把可见类库换成 Java 8。现在只改编译选项javac-encodingUTF-8--release8-dout/release8 src/ApiTrap.java这次编译失败实际诊断中出现错误: 找不到符号 System.out.println( .isBlank()); ^ 符号: 方法 isBlank() 位置: 类 String--release 8同时约束对应的语言规则、目标类文件格式和所能使用的该版本平台 API。它在编译时发现了越界调用。javac 的选项说明定义了这三项约束。再把--release改为11javac-encodingUTF-8--release11-dout/release11 src/ApiTrap.javajava-cpout/release11 ApiTrap相同代码可以编译输出的 class major 为55。这份类在本次 JDK 21 上运行输出true。Java 8 运行时没有在这里执行前两项证据已经足够说明一个 Java 8 格式的 class仍然可能引用 Java 8 不存在的方法。本次编译方式结果观察到的类文件-source 8 -target 8成功major 52仍引用isBlank()--release 8找不到isBlank()本次编译未成功--release 11成功major 55图1同一份isBlank源码的三组编译结果。major52只说明class格式release8在编译阶段拒绝API越界release11产物在本次JDK21运行。在 Maven 中把这个约束交给编译插件下面是一个可单独运行的 POM代码按 Maven 目录放到src/main/java/ApiTrap.java。它故意把目标设为8用来重现上面的 API 拒绝projectxmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersiongroupIdexample.fixture/groupIdartifactIdcompiler-release-check/artifactIdversion1.0-SNAPSHOT/versionpropertiesmaven.compiler.release8/maven.compiler.releaseproject.build.sourceEncodingUTF-8/project.build.sourceEncoding/propertiesbuildpluginsplugingroupIdorg.apache.maven.plugins/groupIdartifactIdmaven-compiler-plugin/artifactIdversion3.16.0/version/plugin/plugins/build/project可直接调用指定版本的编译目标mvn org.apache.maven.plugins:maven-compiler-plugin:3.16.0:compile本次 Maven 为3.9.12。在各自全新、没有旧 class 的两个实验项目里release8因isBlank()编译失败release11的构建成功。验证使用独立 settings、用户目录和本地仓库没有执行install或deploy。日常项目通常通过mvn clean verify做干净构建和验证。改完编译基线应确认日志确实重新编译了源码只看到BUILD SUCCESS还不够。本次复跑一个已有输出目录时就曾看到Nothing to compile随后改用每组全新目录做差异验证避免让上一轮 class 掩盖结果。对于 JDK 9 及以上的构建优先用maven.compiler.release表达目标平台。JDK 8 自身不支持javac --release较新的 Compiler Plugin 对在 JDK 8 下使用release属性有兼容处理仍应按实际插件版本核对。Maven 的目标 Java 版本指南同时说明了这些条件不能把一个属性片段当作所有构建环境通用的保证。java.version 有没有作用要看谁引用它java.version不是 Maven 自动识别的“切换 JDK”开关。一个普通 POM 里即使写了这个名字也要有父 POM、插件或其他配置引用它才会产生对应效果。在继承spring-boot-starter-parent的项目中这条引用是明确存在的。比如 3.5.16 的父 POM 将maven.compiler.release设为${java.version}。沿用这套父配置时通常只需要覆盖一个来源propertiesjava.version17/java.version/properties如果项目只是在dependencyManagement中导入spring-boot-dependencies它得到的是依赖版本管理不会因此继承 starter parent 的插件管理和全部属性关系。这时应显式管理编译插件及maven.compiler.release。两种接法的差异见 Spring Boot 的 Maven 配置说明。所以没必要为了“保险”把java.version、source、target、release全部写一遍。一处后来改成 21另一处还停在 17反而会增加排查成本。先确认当前父配置如何传值再保留能表达项目目标的配置。镜像构建也需要区分。spring-boot:build-image会结合编译目标配置及所选 builder/buildpacks 处理运行时 Java 版本也允许专门覆盖不能把它简化为“只要有 java.version 就会拉取对应基础镜像”。build-image 的说明列出了这条处理路径和覆盖入口。图2沿父POM引用找到编译目标。只导入BOM不会继承这条父配置链release表达目标平台实际JDK仍由构建环境和Toolchains等决定。最后核对运行时与依赖的最低要求mvn -version可以看到运行 Maven 的 Java 版本。若配置了 Toolchains实际编译器还可能来自另一套 JDKrelease只表达编译目标不会替你安装或切换编译器。Maven Toolchains解决的是后一个问题。release也不会把依赖重新编译成低版本。上面的 Java 8 例子是独立 Java 项目用它说明 API 检查不能据此把现代 Spring Boot 应用降成 Java 8。比如 Spring Boot 3.5 的系统要求明确要求至少 Java 17框架本身的要求仍然成立。排查部署异常时可以沿两条线继续UnsupportedClassVersionError先核对报错类的 class major 与运行时版本NoSuchMethodError还要核对实际加载的类和依赖版本不能只盯着source/target。缺失的方法可能来自 JDK API 越界也可能来自第三方依赖冲突。POM 把编译意图写清楚干净构建检查实际产物目标运行环境再验证依赖和行为。这几个检查各自有范围release能把平台 API 错用提前到编译阶段却不能替代部署验证。2026-11-02 修订补充 JDK/Maven 对照实验明确 Spring Boot 父 POM 与 BOM 的差异修正 java.version 和镜像构建关系的概括并补充增量编译及依赖兼容边界。