1. 现象描述
随着 Spring Boot 4 和 Kotlin 2.1 的升级,在我代码中原本正常的泛型封装代码突然编译报错了。
先看一下具体场景,很多时候大家都会对 RestTemplate 和 RestClient 进行一下封装,设置一些通用的请求参数,如鉴权。
1 | // 这是一个简单的RestTemplate封装 |
1 | // 这是一个简单的RestClient封装 |
但在升级 Spring Boot 4 和 Kotlin 2.1 之后,编译时发现出现了以下的错误。
1 | Type argument is not within its bounds: must be subtype of 'Any' |
而且错误的位置竟然是在参数上
1 | private fun <T> request( |
这是怎么回事呢?
2. 核心原因
这次报错是由两个底层重大变更共同导致的:
- Spring Boot 4 引入 JSpecify 规范
- Kotlin 2.1 把 JSpecify 的 null 检查改为严格
1. Spring Boot 4 引入 JSpecify 规范
首先什么是 JSpecify ?
曾经在 Java 里使用标注类型安全的 API,比如 @NonNull 和 @Nullable,会发现有无数的包都提供了这两个注解,至少有 javax(jakarta)的、 Lombok 的、JetBrains 的。

但不管用哪个,总的来说它只是在 IDE 里(可能)会给你做出一点提示,但在编译器不会有效果。
既然 Java 本身一直都没有原生提供这个 Null 检查的能力,几家大厂联合起来就推出了一个 JSpecify 规范,以后这种 @NonNull 和 @Nullable 都用 JSpecify 的就没错了。
Spring Boot 4 基于的 Spring Framework 7 就全面引入了 JSpecify 的注解,并且在 package-info.java 中添加了 @NullMarked 注解,表示这个包下的所有类和接口都是严格 null 安全的。
所以,ParameterizedTypeReference<T> 的泛型参数 T 现在是非空类型的。
注:如果要定义一个可空的泛型,现在需要这么做 (Java)
1 | public class NumberList<E extends Number> implements List<E> {...} |
B. Kotlin 2.1 把 JSpecify 的 null 检查改为严格
Kotlin 语言是由 JetBrains 开发的,而 JSpecify 其中重要的一员也是 JetBrains,那么它肯定要让 Kotlin 也能从 JSpecify 中收益。
在 Kotlin 2.1 之前,Kotlin 编译器对 Java 库中的 JSpecify 注解(如 @NonNull, @Nullable)通常只报 Warning。
但从 Kotlin 2.1 开始,为了提升类型安全性,编译器默认将这些不匹配提升为 Error。
C. Kotlin 泛型的默认行为
还有容易忽略的一点:
- 在 Kotlin 中,
<T>实际上等同于<T : Any?>,它是可空的。 - 而一个
@NullMarked的 Java 泛型,<T>等于<T : Any>,即非空的。
3. 解决方案
添加显式的类型声明即可。需要明确告诉编译器,这个泛型 T 是一个非空类型(Any),写成 <T : Any>。
1 | // 修复:添加 : Any 约束 |