Paolo
05/04/2026, 4:04 PMFile object evaluates to false if the file does not physically exist, whereas previously it only required the reference to be non-null.
While this change impacts the Nextflow codebase, our primary concern is the broader ecosystem:
• Critical External Impact: This change is a “silent breaker” for the thousands of third-party scripts and applications running on Nextflow. Users expecting legacy Groovy behavior will encounter unexpected logic failures that are notoriously difficult to debug in a pipeline context.
• Internal Management: While we can refactor our own core codebase to accommodate the new semantics, we cannot realistically control or force a synchronized update across the entire community of external plugins and user-maintained pipelines.
To mitigate this, I’ve submitted this PR to introduce a system option that allows reverting to the previous behavior. This ensures backward compatibility and provides a necessary safety net for the community as they transition.
Hope you will consider this for merge!paulk_asert
05/05/2026, 2:15 AMglaforge
05/31/2026, 8:08 PMpaulk_asert
05/31/2026, 9:20 PMpaulk_asert
05/31/2026, 9:22 PMglaforge
06/01/2026, 5:49 AMJefferoson Hilario
07/11/2026, 1:15 AMSean Williams
07/21/2026, 12:15 AM<http://groovyx.net|groovyx.net>.http.RESTClient. Is there some guidance for matching some of the config patterns (combo closure+dictionary support, etc.) seen with get(), post() , request(), and so on?Sean Williams
07/21/2026, 12:17 AMpaulk_asert
07/21/2026, 1:51 AMSean Williams
07/21/2026, 1:52 AMOctavia Togami
07/22/2026, 2:44 AMSujith
08/14/2026, 7:44 PMVampire
08/21/2026, 5:11 PM@CompileDynamic over @TypeChecked(SKIP)?
From what I tested, @TypeChecked(SKIP) implies @CompileDynamic but not the other way around.
So if the code in general is caused to be type-checked due to @TypeChecked or a compiler config script doing it, you can only use @TypeChecked(SKIP) to not type-check some code.
If the cause for type-checking is @CompileStatic or a compiler config script doing it, then both can be used.
So I really wonder when I ever would want to use @CompileDynamic.paulk_asert
08/21/2026, 9:29 PM@TypeChecked(SKIP) opts out of type checking, and since the type info gathered during checking is what static compilation is built on, it opts out of that too.
@CompileDynamic means the code needs dynamic dispatch. It would make sense on a method requiring dynamic features but still checkable - type checking could then be toggled at the class level via @TypeChecked. If the dynamic features also mean that checking is not possible, then the SKIP
path shows that intent.Vampire
08/22/2026, 11:40 AMpaulk_asert
08/22/2026, 10:41 PM1. Duplicate case labels in a switch expression (both int and String reproduce): @TypeChecked compiles fine (dynamically legal — first match wins, second is dead code), but @CompileStatic fails with "Duplicate case label"
2. @CompileStatic on a constructor inside a non-statically-compiled class that has fields, properties, or instance initializers → "Cannot statically compile constructor implicitly including non-static elements…"AI Advice for framework code:
If this is happening in third-party framework code, the usual cause is an AST transform or type-checking extension rather than your code. Check whether the framework applies transforms that generate/rewrite code after type checking — the static compiler needs each call annotated with its resolved target (DIRECT_METHOD_CALL_TARGET / INFERRED_TYPE node metadata), and code injected post-STC won't have it, so it compiles fine dynamically and under @TypeChecked but fails under @CompileStatic. Errors like "Target method … hasn't been set", or VerifyErrors only under static compilation, are the tell. If the framework ships a type-checking extension, it should be using makeDynamic() (or resolving real method targets) for the calls it vouches for.
Vampire
08/23/2026, 3:33 AMThere are obvious ways to make it happen (never a good idea), e.g. create a type checking extension that "ignores some error about missing type info" and proceeds.That's only obvious if you are aware of type checking extensions, which I was not up to now, so thanks for mentioning them, I could actually right now need to use them. 😄
Two places AI just found where Groovy itself breaks the rule (I'll investigate these separately):In my case it was access to a
private static final Map.
Type-check was happy with that, compile-static was not.
A call to a private static method was already complained about by the Type-checker.
So here it also seems obvious to me that the type-checker should complain, just wanted to know the general rule.paulk_asert
08/23/2026, 6:05 AMWhy the asymmetry exists: STC's method selection filters candidates by visibility, so a private static method of another class is rejected up front in both modes. But its property/field resolution doesn't enforce visibility — it happily resolves P.M and infers Map. Under @TypeChecked that goes unnoticed because runtime dispatch is dynamic and Groovy is historically lenient about private access, so the code even runs. Under @CompileStatic, StaticTypesCallSiteWriter has to emit a direct GETSTATIC, re-checks accessibility at codegen time, finds no bridge method (those only exist for nestmate-style relationships — same class, closures, inner classes), and refuses with the late, non-STC-flavored "Access to P#M is forbidden". The workaround (@PackageScope, or exposing a getter) is straightforward.Probably should be fixed - probably a breaking change for some edge case.
paulk_asert
08/23/2026, 6:11 AMVampire
08/23/2026, 8:27 AM.@ to work-around.
Is there also something similar for calling a private method?
If not (or anyway) is there some way to doing something ad-hoc dynamically like makeDynamic in stc extension but in the actual code? Like dynamic { foo.privateMethod() as String } besides moving it to a helper method and marking that with SKIP I mean.jonnybot
08/24/2026, 8:26 PMVampire
08/27/2026, 2:52 AMclass Foo {
def foo() {
{ Object[] foo -> println(foo) }.call(new Object[] { 'foo' })
}
}
new Foo().foo()
it prints [foo].
Adding @groovy.transform.CompileStatic,
it prints [[foo]].
Removing the Object[] with added static-compilation it again prints [foo] .Vampire
08/27/2026, 9:11 AM@Grab('jakarta.faces:jakarta.faces-api:4.0.1')
import jakarta.faces.component.UIInput
@groovy.transform.CompileStatic
class Foo {
def foo() {
println(UIInput.PropertyKeys)
println(UIInput.PropertyKeys['localValueSet'])
println(UIInput.PropertyKeys.localValueSet)
}
}
new Foo().foo()
this fails at runtime at the third println (issues.apache.org/jira/browse/GROOVY-12310) with message
Besides that STC should probably already fail (hence the issue), the first two `println`s work currently. Is there there some syntax trick (likeCopy codejava.lang.IllegalAccessError: failed to access class jakarta.faces.component.UIInput$PropertyKeys from class Foo
.@ for private properties and .&...() for private methods) that would make this work while keeping the IDE reference to localValueSet intact instead of the String-y access without using a dynamic helper method?Vampire
09/01/2026, 6:10 AMVampire
09/11/2026, 1:53 PMava.lang.NoSuchMethodError: 'org.codehaus.groovy.ast.stmt.TryCatchStatement org.codehaus.groovy.ast.stmt.TryCatchStatement.addCatch(org.codehaus.groovy.ast.stmt.CatchStatement)'
at org.spockframework.compiler.ConditionRewriter.surroundWithTryCatch(ConditionRewriter.java:796)
at org.spockframework.compiler.ConditionRewriter.rewriteOtherCondition(ConditionRewriter.java:739)
at org.spockframework.compiler.ConditionRewriter.rewriteCondition(ConditionRewriter.java:654)
at org.spockframework.compiler.ConditionRewriter.rewriteCondition(ConditionRewriter.java:626)
at org.spockframework.compiler.ConditionRewriter.rewriteImplicitCondition(ConditionRewriter.java:102)
at org.spockframework.compiler.DeepBlockRewriter.handleImplicitCondition(DeepBlockRewriter.java:234)
at org.spockframework.compiler.DeepBlockRewriter.doVisitExpressionStatement(DeepBlockRewriter.java:89)
at org.spockframework.compiler.AbstractDeepBlockRewriter.visitExpressionStatement(AbstractDeepBlockRewriter.java:105)
at org.codehaus.groovy.ast.stmt.ExpressionStatement.visit(ExpressionStatement.java:41)
at org.spockframework.compiler.StatementReplacingVisitorSupport.replace(StatementReplacingVisitorSupport.java:44)
at org.spockframework.compiler.AbstractDeepBlockRewriter.visit(AbstractDeepBlockRewriter.java:89)
at org.spockframework.compiler.DeepBlockRewriter.visit(DeepBlockRewriter.java:60)
at org.spockframework.compiler.SpecRewriter.visitAnyBlock(SpecRewriter.java:485)
at org.spockframework.compiler.model.ExpectBlock.accept(ExpectBlock.java:31)
at org.spockframework.compiler.model.Method.accept(Method.java:70)
at org.spockframework.compiler.model.Spec.accept(Spec.java:112)
at org.spockframework.compiler.SpockTransform$Impl.processSpec(SpockTransform.java:73)
at org.spockframework.compiler.SpockTransform$Impl.visit(SpockTransform.java:62)
at org.spockframework.compiler.SpockTransform.visit(SpockTransform.java:48)
at org.codehaus.groovy.transform.ASTTransformationVisitor.lambda$5(ASTTransformationVisitor.java:453)
at org.codehaus.groovy.control.CompilationUnit$ISourceUnitOperation.doPhaseOperation(CompilationUnit.java:939)
at org.codehaus.groovy.control.CompilationUnit.processPhaseOperations(CompilationUnit.java:678)
at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:652)
at org.codehaus.jdt.groovy.internal.compiler.ast.GroovyCompilationUnitDeclaration.processToPhase(GroovyCompilationUnitDeclaration.java:243)
at org.codehaus.jdt.groovy.internal.compiler.ast.GroovyCompilationUnitDeclaration.resolve(GroovyCompilationUnitDeclaration.java:639)
at org.eclipse.jdt.internal.compiler.Compiler.process(Compiler.java:833)
at org.eclipse.jdt.internal.compiler.ProcessTaskManager.processing(ProcessTaskManager.java:135)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:545)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:328)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614)
at java.base/java.lang.Thread.run(Thread.java:1474)
I'm debugging Eclipse right now and sitting at a breakpoint where it happened.
Both instances come from the same class loader.
Also, doing tryCatchStatement.addCatch(catchStatement) in the debugger evaluation works just fine without exception. But the breakpoint is in the constructor of the NoSuchMethodError, so it just happened at that exact place. 😕
It even happens consistently and reproducibly.
If I recompile GuiceMultiModules when it tries to transform the assert service1 instanceof IService1.
Or also any of the other 5 explicit and implicit assertions.
Even if I have only one expect: true it happens.
Even if I boil down the whole class to just
package org.spockframework.guice
import spock.lang.Specification
class Foo extends Specification {
def foo() {
expect: true
}
}
I even took out the debugger evaluation out of the loop, writing those 5 expressions to a text-file and when the breakpoint in the exception hit looked at the text file, still the classloader is the same.ysb33r
09/20/2026, 8:26 AM@POJO?
• Should also have an accompanying @CompileStatic
• Extending a Groovy class that is not @POJO extended will always have the GroovyObject methods even in the derived class are annotated with @POJO .
• It is not neccessary to annotate interfaces in Groovy with @POJO.paulk_asert
09/24/2026, 8:32 PMVampire
10/06/2026, 5:39 PMVariableExpression.THIS_EXPRESSION the STC and thus static-compile gets confused.
We use this at some places in the Spock transform.
While trying to make Spock specs compatible with STC and SC, it was observed that STC stores type information at the current node and using VariableExpression.THIS_EXPRESSION then causes conflicting casts when used inside and outside a closure.
As a work-around we can instead use new VariableExpression("this"), but is this expected, or should it work properly and I should report a bug?
As an example.
class ASpec extends Specification {
def "mock init closure"() {
given:
List<String> list = Mock(List) {
size() >> 3
}
when:
int size = list.size()
then:
size == 3
}
}
compiles in semantic analysis to
import spock.lang.*
@org.spockframework.runtime.model.SpecMetadata(filename = 'script.groovy', line = 1)
public class ASpec extends spock.lang.Specification {
@org.spockframework.runtime.model.FeatureMetadata(name = 'mock init closure', ordinal = 0, line = 2, blocks = [@org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.SETUP, texts = []), @org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.WHEN, texts = []), @org.spockframework.runtime.model.BlockMetadata(kind = org.spockframework.runtime.model.BlockKind.THEN, texts = [])], parameterNames = [])
public void $spock_feature_0_0() {
org.spockframework.runtime.ValueRecorder $spock_valueRecorder = new org.spockframework.runtime.ValueRecorder()
org.spockframework.runtime.ErrorCollector $spock_errorCollector = org.spockframework.runtime.ErrorRethrower.INSTANCE
org.spockframework.runtime.SpockRuntime.callBlockEntered(this, 0)
java.util.List<String> list = org.spockframework.runtime.SpecInternals.MockImpl(this, 'list', java.util.List, java.util.List, { ->
this.getSpecificationContext().getMockController().addInteraction(new org.spockframework.mock.runtime.InteractionBuilder(5, 7, 'size() >> 3').addEqualTarget(it).addEqualMethodName('size').setArgListKind(true, false).addConstantResponse(3).build())
})
org.spockframework.runtime.SpockRuntime.callBlockExited(this, 0)
org.spockframework.runtime.SpockRuntime.callBlockEntered(this, 1)
java.lang.Integer size = list.size()
org.spockframework.runtime.SpockRuntime.callBlockExited(this, 1)
org.spockframework.runtime.SpockRuntime.callBlockEntered(this, 2)
try {
org.spockframework.runtime.SpockRuntime.verifyCondition($spock_errorCollector, $spock_valueRecorder.reset(), 'size == 3', 12, 5, null, $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(2), $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(0), size) == $spock_valueRecorder.record($spock_valueRecorder.startRecordingValue(1), 3)))
}
catch (java.lang.Throwable $spock_condition_throwable) {
org.spockframework.runtime.SpockRuntime.conditionFailedWithException($spock_errorCollector, $spock_valueRecorder, 'size == 3', 12, 5, null, $spock_condition_throwable)}
finally {
}
org.spockframework.runtime.SpockRuntime.callBlockExited(this, 2)
this.getSpecificationContext().getMockController().leaveScope()
}
}
The important part here is the two getSpecificationContext() calls, one inside the mock init closure, one as last line in the feature method, both added by the Spock transform.
If those use VariableExpression._THIS_EXPRESSION_ then at runtime you get java.lang.ClassCastException: apackage.ASpec cannot be cast to apackage.ASpec$__spock_feature_0_0_closure1.
If you use new VariableExpression("this") instead, it works as intended.Jefferoson Hilario
10/09/2026, 12:46 AM