JDK 28 的 javac 开始检查 switch 的 when 守卫。守卫里抛 IOException,在 27.0.1.0.3 上能编过,套 try/catch (IOException) 反而报 never thrown,跑起来照样炸。换到 28-ea+21-1707,同一份源码直接要求 catch 或 throws。

根因是 JDK-8390759。旧 Flow 分析扫 case 标签和分支体,漏了 guard。受检异常在编译期被“藏起来”,运行期还是会抛。JDK 28 把守卫重新纳入异常与可达性分析。

谁会撞上

业务里用模式匹配 switch,在 when 里调文件、网络、JDBC 这类声明了受检异常的方法。Spring Boot 控制器把 payload 分类,守卫里读 marker 文件大小,就是典型路径。

更绕的是:你在 27 上已经意识到可能抛 IOException,想包一层 catch,javac 却说 try 里根本不会抛。于是只好吞 Exception,或者干脆不处理。CI 升到 28 后,未处理的那条路径开始编译失败。

现场长什么样

最小探针(与 JBS / 官方测试同款):

import java.io.IOException;

public class GuardUnreported {
    private static boolean guard() throws IOException {
        throw new IOException("boom");
    }

    public static void main(String[] args) {
        Object o = "x";
        switch (o) {
            case String s when guard() -> System.out.println("hit");
            default -> System.out.println("other");
        }
    }
}

实测(两边都加 --release 21):

===== javac javac 27.0.1.0.3 :: probes/GuardUnreported.java =====
java openjdk version "27.0.1.0.3" 2026-10-25
exit=0
Exception in thread "main" java.io.IOException: boom
	at GuardUnreported.guard(GuardUnreported.java:5)
	at GuardUnreported.main(GuardUnreported.java:11)
run_exit=1

===== javac javac 28-ea :: probes/GuardUnreported.java =====
java openjdk version "28-ea" 2027-03-23
probes/GuardUnreported.java:11: error: unreported exception IOException; must be caught or declared to be thrown
            case String s when guard() -> System.out.println("hit");
                                    ^
1 error
exit=1

再看 catch。27 上写了 catch,编译器不认;28 上同一段能编过,运行还能接到消息:

===== javac javac 27.0.1.0.3 :: probes/GuardCatch.java =====
probes/GuardCatch.java:15: error: exception IOException is never thrown in body of corresponding try statement
        } catch (IOException e) {
          ^
1 error
exit=1

===== javac javac 28-ea :: probes/GuardCatch.java =====
exit=0
caught: boom
run_exit=0

方法上声明 throws IOException,两边都能编过,运行时行为一致,都会抛 boom。

发行说明里另一半是可达性。守卫里嵌套 switch expression,yield 后面再写语句,27 放过,28 报 unreachable:

===== javac javac 27.0.1.0.3 :: probes/GuardUnreachable.java =====
exit=0

===== javac javac 28-ea :: probes/GuardUnreachable.java =====
probes/GuardUnreachable.java:6: error: unreachable statement
                case 0 -> { yield false; System.out.println(); }
                                         ^
1 error
exit=1

怎么确认

固定两套 javac:27.0.1.0.3+1-2 与 28-ea+21-1707,都加 --release 21。上面矩阵已经说明:语言级别没变,变的是编译器对守卫的分析。

落到 Spring Boot 4.1.2。服务里用模式匹配分类请求,守卫调用声明了 IOException 的私有方法:

public String classifyHidden(Object payload, Path marker) {
    return switch (payload) {
        case String s when existsNonEmpty(marker) -> "string-with-marker";
        case String s -> "string-plain";
        case Integer n when n > 0 -> "positive-int";
        default -> "other";
    };
}

private static boolean existsNonEmpty(Path marker) throws IOException {
    return Files.isRegularFile(marker) && Files.size(marker) > 0;
}

JDK 27.0.1.0.3 上 mvn -DskipTests clean package exit=0。接口实测:

GET /classify?kind=hello
{"result":"string-plain"}

GET /classify?kind=hello&marker=/tmp/sg-marker.txt
{"result":"string-with-marker"}

GET /classify?kind=int
{"result":"positive-int"}

同一份源码换 28-ea+21,maven-compiler-plugin 3.15.0,release 仍是 21:

[INFO] Compiling 3 source files with javac [debug parameters release 21] to target/classes
[ERROR] COMPILATION ERROR :
[ERROR] PayloadGate.java:[27,46] unreported exception java.io.IOException; must be caught or declared to be thrown
[INFO] BUILD FAILURE

连带断点

  • 27 上“正确写法”catch IOException,会先被 javac 挡下。别把 never thrown 理解成“运行也不会抛”。
  • 吞 Exception / Throwable 能骗过 27,但会把守卫里的受检异常和其他运行时问题搅在一起。28 升上来之后,更干净的做法是声明 throws,或在守卫外先算好布尔条件。
  • 本机 IDE 仍绑 27 的 javac、CI 已是 28,会表现为本地绿、流水线编译红。对齐工具链版本再比。
  • 和 raw/参数化三元那条(JDK-8385925)不是一回事。那条是结果类型判定;这条是守卫的异常与可达性漏扫。

升级怎么选

升 JDK 28 工具链前,先搜 when 后面有没有调声明了受检异常的方法。能编过的旧代码,优先改成:守卫只做纯判断,IO 放在 switch 外;或者给方法补 throws / 局部 try-catch。

已经写了 catch IOException 却在 27 上编不过的,换 28 后反而能过,这是预期修复,不是回归。

参考:JDK-8390759,JDK 28 Early-Access Release Notes(2026-11-02 更新稿含本条)。

— 感谢阅读 —
广告

一起交流

分享你的思考,让讨论更进一步。