Apache Groovy 6.0已经正式发布。这不是一次修修补补的小迭代,而是Groovy面向JDK 17+、虚拟线程(Virtual Threads)、云原生运行时与当代工程工具链的一次系统性升级。
作为运行在JVM上的动态/静态混合语言,Groovy长期承担三重职责:Java生态中高生产率的胶水与脚本语言,企业级领域特定语言(DSL)、构建与测试的重要实现手段,以及把复杂业务规则写成可读代码的建模工具。Groovy 6在这三方面同步推进:并发可以用接近同步代码的顺序风格表达;标准库更接近开箱即用(batteries-included);方法声明开始具备可被编译器核实的规格(specification)意义;动态分发则在语义保持不变的前提下,获得更稳定的热点行为与更低的分配开销。
最低运行环境提升至JDK 17,并已在JDK 17至27上完成验证。运行基线与当代JVM对齐之后,语言、类库与运行时得以按虚拟线程、模块系统与原生镜像等能力重新设计。
版本主线
6.0的能力图谱可以概括为四条主线:
- 补齐与现代Java、Kotlin之间长期存在的表达力差距;
- 扩展开箱即用模块,巩固Groovy作为高生产率JVM脚本与集成语言的定位;
- 使并发代码可以自上而下阅读,而不必依赖回调或Future组合子堆叠;
- 将方法写成规格:对人可读,对编译器可验证,对工具链与智能辅助编码可依赖。
需要同时看清稳定面与孵化面。密封类型(sealed types)、GINQ、groovy-toml、契约模块的核心API、正则与格式串检查器、宏框架(macro framework)以及JavaShell已在本版转正(stabilize)。原生async/await、整合并发套件、HTTP客户端、循环级AST变换、单子推导(monadic comprehension)、联合编译存根增强、动态代码的GraalVM Native Image支持等,仍以孵化(incubating)形式进入主线——方向明确,适合在新项目与受控试点中采用,但公共API在后续小版本中仍可能调整。
原生异步:顺序书写,并发执行
以往在Groovy中编写异步逻辑,通常需要在CompletableFuture链式调用、外部响应式库或独立并行框架之间选择。Groovy 6提供语言级的async/await(孵化):同一段“先获取画像、再获取任务列表、再筛选”的逻辑,可以写成与同步代码同构的顺序结构,异常处理回到普通的try/catch。
执行器选择由运行时完成:JDK 21+默认使用虚拟线程;JDK 17–20回退到缓存线程池(cached thread pool)。开发者描述业务步骤,平台负责调度。
组合面一次齐备,而不是只提供关键字:
Awaitable.all与多参数await(a, b, c)等待全部完成;any表示竞速(race),first取首个成功结果,allSettled逐项检视完成状态;timeout与非阻塞delay;- 带反压(back-pressure)的
yield return异步生成器(generator); - Go风格的
AsyncChannel,以及用for await消费生成器、通道与响应式流; AsyncScope将子任务生命周期绑定到作用域,实现结构化并发(structured concurrency);defer按后进先出(LIFO)注册清理动作,无论成功或失败均会执行。defer仅在async闭包内作为上下文关键字(contextual keyword)解析,不影响既有代码中的同名标识符。
CompletableFuture与Future可直接作为await目标。可选模块groovy-reactor、groovy-rxjava使Project Reactor的Mono/Flux以及RxJava 3的常用类型进入同一套组合子(combinator)。既有反应式资产不必推倒重来,可以逐步被顺序风格封装。
高阶并发进入核心库,并向Java开放
groovy.concurrent(孵化)将Agent、Actor、数据流(dataflow)、通道(channel)与并行集合,按虚拟线程与结构化并发重新实现。熟悉GPars的团队会看到对应概念,但这些能力现已进入语言核心,并与await使用同一套组合方式。
- Agent:以串行函数更新线程安全状态,从模型上避免数据竞争(data race);状态变化可通过
Publisher被for await消费。 - Actor与
@ActiveObject:既可手写消息协议,也可为普通类添加注解,使指定方法在串行邮箱(mailbox)上执行。调用方使用普通方法调用,不必自行定义消息类型与分发循环。非阻塞方法返回Awaitable。生产路径还提供停止哨兵(sentinel)、错误回调、有界邮箱与溢出策略(overflow policy)。 DataflowVariable:单赋值(single-assignment)变量,读与写的先后顺序不影响正确性;Dataflows在属性访问时自动创建变量。- 并行集合:
collectParallel、findAllParallel、eachParallel、sumParallel、injectParallel等面向CPU密集型负载;Pool与ParallelScope用于隔离CPU池与I/O池。循环上的@Parallel(孵化)与上述设施共享:JDK 21+使用虚拟线程,否则回退平台线程(platform threads)。 - 通道组合与选择:支持过滤、映射、合并、拆分与广播,并提供完整的通信顺序进程(CSP, Communicating Sequential Processes)选择——混合发送/接收、守卫条件(guard)、公平或随机策略、定时分支。未选中的分支不会误消费通道中的值。
同一套底层API通过独立模块groovy-concurrent-java提供给Java、Kotlin及其他JVM语言,无需引入完整Groovy运行时。该模块与完整Groovy运行时互斥,不宜同时出现在同一类路径上。async/await/defer/@ActiveObject以及并行集合扩展方法仍依赖完整Groovy;跨语言共享的是结构化并发、Agent、Actor、通道与线程池等原语。
官方给出的分工同样明确:CPU密集负载使用并行集合;I/O密集负载使用虚拟线程上的async/await。ForkJoin工作线程不应被阻塞式网络或睡眠占用。
语言人体工程学:加法演进,语义兼容
Groovy 6的语法扩展以降低样板代码(boilerplate)为目标,并与Java、Kotlin的日常写法对齐;对既有合法程序保持加法兼容。
val是var的不可变伴侣。val name = 'Groovy'等价于final def,类型推断规则与var相同。需要注意的是浅层不可变(shallow finality):绑定本身不可重新赋值,但若右值是可变集合,其内容仍可修改。val为上下文关键字,用作变量名、方法名或Map键的既有代码仍然有效。需要更长迁移周期时,可用系统属性暂时关闭该关键字。
多重赋值解构(destructuring)支持剩余绑定(rest binding)与映射风格绑定。例如def (head, *tail) = list、def (name: n, age: a) = person,剩余绑定可带类型标注,外层final会传播到各个绑定,_用于丢弃不需要的位置。Groovy 4/5中合法的程序在6中语义不变。
复合赋值(compound assignment)支持就地更新(in-place mutation)。过去a += b一律改写为a = a.plus(b),可变容器因此反复分配新对象,final左值也无法使用复合赋值。类型现在可以提供plusAssign、minusAssign等方法;解析到这些方法时,运算符就地修改接收者,并允许作用于静态检查下的final字段。未找到对应方法时仍使用既有语义。
不可变对象与Record的copyWith支持嵌套更新。点路径(dotted path)键可写为user.copyWith('address.city': 'Shanghai')。未修改的子结构保持结构共享(structural sharing),引用同一性(identity)得以保留。块形式允许从更新前的old派生新值;空更新保持同一实例。路径上的每个节点都必须自身提供copyWith。
循环现可作为AST变换目标(孵化):@Parallel使迭代并行执行;循环不变式(loop invariant)与终止度量(termination measure)也可直接标注在循环上。面向变换作者的流畅AST查询API(孵化)把带可变状态的访问器改写为声明式查询。
Trait静态成员模型(孵化增强)更为完整:实现类各自持有静态状态副本;需要静态模板方法(template method)时,可用@Virtual使Trait体内调用尊重实现类的覆盖。框架中常见的“可覆盖默认策略”由此获得可编译检查的语义。
与Java的语法及工具链对齐
Groovy 6有意识地收窄与Java的语法、语义与构建缝隙,降低从Java复制代码时的改写量,也降低混合工程中的工具链摩擦。
import module java.sql可一次引入该模块导出的全部公共类型,并覆盖传递依赖(requires transitive)所导出的包。显式单类型导入优先,可用于消解名称冲突。该能力在JDK 17上即可使用。
交集类型转换(intersection-type cast)可用于lambda、方法引用与闭包,例如(Runnable & Serializable) () -> …。当边界包含Serializable时,静态编译会生成序列化所需的配套成员,便于跨JVM传递,或进入依赖序列化函数式接口的框架。
switch按Java规则区分两种角色:箭头决定是否贯穿(fall-through),语法位置决定它是语句还是表达式。用作语句的箭头switch不必穷尽(exhaustive);静态编译下的switch语句生成tableswitch/lookupswitch,并报告重复标签。泛型语法进一步向Java语言规范(JLS)靠拢;instanceof模式变量的流作用域(flow scoping)、注解中的数组初始化等细节一并补齐。结构模式匹配本身不是6.0的内容。
对混合语言项目更具工程意义的是联合编译存根(joint-compilation stub,该项增强仍为孵化)。@Immutable、@Builder、@TupleConstructor、@Delegate等变换生成的构造器与方法会进入供javac使用的存根;原生Record、Trait静态方法以及密封类型的sealed/permits声明同样可见。Java源文件可以在同一编译单元中直接调用这些由变换贡献的成员。
方法即规格:契约、纯度与可机械执行的边界
若把Groovy 6放进大型工程与智能辅助编码的语境中,本版最值得关注的设计是:让方法声明成为自描述、可校验的规格。
契约(contract)模块的核心注解@Requires、@Ensures、@Invariant已经稳定,并且可以用于脚本。循环级@Invariant、终止度量@Decreases,以及描述可修改状态范围的@Modifies(帧条件,frame condition),仍依赖孵化中的循环AST变换基础设施,本版继续处于孵化状态。@ThrowsIf用于声明“当且仅当某条件成立时抛出某异常”,既可织入运行时检查,也可仅作为机器可读文档。
@Pure声明方法无外部副作用(side effect),由纯度检查器(Purity Checker)在编译期核实。@Modifies由修改集检查器(Modifies Checker)核实方法体是否只改写声明范围内的状态。一旦声明被编译器证明,后续分析就可以依据前置条件(precondition)、后置条件(postcondition)与修改集推断调用序列的效果,而不必展开每个方法体。这对人工审阅、重构、测试生成以及编码助手都有直接价值:助手依赖的是被核实的声明,而不是对方法体的猜测。
空安全(null-safety)分析同步增强。空值检查器识别常见的@Nullable/@NonNull注解,理解Groovy的真值守卫、安全导航(safe navigation)、早期返回,以及requireNonNull与测试断言库中的非空断言。严格模式可在无注解代码上做流敏感(flow-sensitive)分析,并检查显式非空字段是否被全部构造器初始化。
并行归约(parallel reduction)补上了测试难以稳定复现的一类错误:结合律(associativity)。并行inject/sum会切分、重排再合并数据;减法、除法等非结合运算会在不同分区下得到不同结果。结合律检查器在编译期拦截高置信问题,并用@Associative/@Reducer把定律写成可被测试派生的声明。需要强调:结合律在一般意义上不可判定,该检查器是保守的安全网,而不是形式化证明。
DO宏(孵化)为Optional、Stream、CompletableFuture、Awaitable以及常见函数式库中的控制类型提供类似Scala for推导(for-comprehension)、Haskell do记法的顺序写法。载体(carrier)资格与链式形状由专门检查器在编译期约束,以避免flatMap返回错误容器这类问题延迟到运行时才暴露。
用户指南新增完整文法专章,将词法与句法以可引用形式给出,供工具作者、语言研究者及需要判定“是否为合法Groovy”的自动化系统使用。
上述机制的目标不是用注解把语言变重,而是为大型代码库与自动化推理提供确定性边界。动态性仍然保留,规格写在方法声明上。
开箱即用的模块与数据格式
Groovy的竞争力不仅在语法,也在完成常见任务时对外部依赖的需求是否足够低。6.0在这一方向继续加码。
新增或从核心拆出的可选模块覆盖脚本与服务开发中的高频缺口:基于JDK HttpClient的HTTP模块(孵化,同时提供命令式DSL与声明式接口,按内容类型解析JSON/XML/HTML,并可由返回类型驱动反序列化,异步结果可与await组合);符合RFC 4180的CSV(孵化);CommonMark Markdown(孵化,便于从文本中抽取代码块、章节与表格);Ivy与Maven Resolver双引擎的@Grab(孵化);JUnit 6脚本运行器;以及Reactor/RxJava适配。经典非invokedynamic调用点缓存已从核心拆出,仅在关闭indy的运行环境中需要额外引入。
HTTP模块按当代客户端的安全默认处理重定向与凭据边界:请求限制在配置的基础地址内;一旦离开原站点,构建器上配置的授权头、Cookie与密钥不会被转发到未知源。
JSON、CSV、TOML、YAML、XML在强类型读路上对齐:同一领域对象可以用一致的方式从不同格式解析,并在类型化路径上保持java.time语义。GINQ转正后新增groupby … into,以及union/intersect/minus/unionall,使内存集合查询更接近SQL习惯。
文档与交互工具同步更新:GroovyDoc支持Markdown文档注释与代码片段嵌入,具备语法高亮与明暗主题;交互式Shell支持内联图像与图表;编译器提供编辑器、持续集成(CI)与编码代理可解析的单行诊断格式,并将未闭合字面量、非法转义、不可见Unicode、缺失标点等常见错误报告为可定位的诊断。
运行时:动态分发保持语义,热点路径降低开销
语法层的效率最终要由运行时承担。Groovy 6在这一层的原则是:优化不改变语义。
动态分发最重要的变化,是元类(metaclass)变更的失效范围从“进程内全部invokedynamic调用点(call site)”收窄为“该接收者元类所拥有的调用点”,即作用域化的invokedynamic失效(scoped invokedynamic invalidation)。此前,修改类A的元类可能使类B、类C上已经内联(inline)的热点去优化(deoptimize)。对依赖元类的框架,以及频繁安装、卸载元类的测试套件,这是全局性开销。6.0之后,局部元类变更不再拖垮其他类型上的稳定热点。动态属性写入默认也走invokedynamic,与读取路径一致。
GDK的each、collect、findAll、inject等增加了面向java.util.function的重载。在静态编译下传入lambda或方法引用时,不再为每次迭代分配Closure,从而得到零分配(allocation-free)路径。官方在特定微基准中给出过约一个数量级的吞吐差异,并同时标明:对比双方的编译模式并不相同,不能把它解读为“所有闭包调用都会快十倍”。复合赋值的就地更新则从语言层减少“一次修改必须分配新对象”的路径。
元对象协议(MOP, Meta-Object Protocol)的热路径对即时编译器(JIT)更友好:方法达到热点阈值后,运行时生成隐藏的同巢类(hidden nestmate),以直接字节码调用目标方法,而不再经过反射。从Java调用多参数闭包(Map遍历、eachWithIndex、inject等)改为按参数个数(arity)索引的快速路径,官方测例显示有倍数级改善。窥孔优化器(peephole optimizer)对局部指令序列做等价改写。实验性的闭包打包(closure packing)可在静态编译下将符合条件的多个闭包收束,以减少类文件数量与Jar体积;该能力默认关闭,按需启用,运行时值仍是真正的Closure。
面向GraalVM Native Image(孵化)与非HotSpot运行时的工作,表明运行时不再默认“当前版本的HotSpot”这一前提。动态分发可以在原生镜像中采用预先链接(AOT link);已经发布的indy字节码无需重编译即可进入镜像构建。隐藏类、版本探测、模块系统缺失、日志初始化等路径改为降级,而不是在类初始化阶段失败。官方同时保持边界:这并不等于宣布Groovy 6已成为受完整支持的Android平台,而是清除了若干使运行时无法启动的假设。
公开基准套件覆盖框架典型的元类密集路径,也覆盖日常惯用法的耗时与分配。性能判断应以具体负载下的测量为准。
默认安全与升级边界
Groovy 6在默认安全策略上明显收紧。XML默认加固外部实体(XXE)与DTD相关风险;SQL在编译期拦截将插值写入引号内的写法,并在运行时拒绝同一模式;正则可设置超时以抑制灾难性回溯(ReDoS);JSON限制嵌套深度;Grape加强对坐标的校验。文档生成、依赖卸载、目录删除等工具路径遵循最小权限,不轻易跟随符号链接离开工作区。安全AST定制器覆盖构造器、初始化块与字段初始化;在编译不受控源码时,嵌入方可以关闭仅用于变换开发的检查注解。
平台层面,6.0移除已过时的安全管理器(Security Manager)集成,将隔离与权限交给容器与操作系统,与当代部署模型一致。
升级时需要核对的行为变化,大多属于“与平台对齐、把隐式约定改为显式约定”:显式类路径(classpath)不再被追加当前目录;字符串到Class的转换不再触发静态初始化(现代JDBC驱动通常经由服务加载器注册);Base64对畸形填充(padding)更严格;Servlet错误响应不再向客户端返回内部路径。val成为上下文关键字,内部类的缺失方法协议收紧,注解目标校验覆盖导入与循环。这些是可预期的边界清晰化,升级时应纳入回归范围。
如何理解这一版的行业含义
把Groovy 6放回2026年的JVM技术版图,其定位清晰。
它没有放弃动态性,也没有把静态检查做成另一门语言。它选择在JDK 17+基线上,用虚拟线程改善并发表达,用可核实的声明降低大型系统与工具链的推理成本,用HTTP、CSV、Markdown与双引擎依赖管理维持脚本生产率,并用作用域化的invokedynamic失效、零分配高阶函数重载以及可预链接的原生镜像路径,证明动态语言不必必然伴随全局去优化与额外分配。
对已经运行在JDK 17或21、维护Grails或企业DSL、将Groovy用于测试与自动化的团队,6.0提供的是可以在试点中测量的改进:并发代码可读性上升,Java混编能够看到变换生成的API,元类相关测试对全局热点的扰动下降,常见的数据与HTTP任务减少对外库的硬性依赖。孵化特性应单独评估,不宜与已转正API同等承诺到长期兼容性条款中。
对仍在观望的团队,更准确的判断是:Groovy仍然是高生产率的JVM语言,并且已经按当代工程标准补齐了并发、性能、安全、工具链与规格化表达上的关键缺口。它是否进入某一具体系统的选型清单,仍取决于团队的语言组成、运维约束与现有资产;但它已经不再只适合被放在“遗留脚本仍在运行”的附录里。
官方发布说明:https://groovy-lang.org/releasenotes/groovy-6.0.html