This is what AI suggests for BytecodeInterfacing. Is that the kind of
thing you had i mind?
package org.apache.groovy.lang.annotation; // next to @Incubating
@Documented
@Retention(RetentionPolicy.RUNTIME) // matches
@Internal/@Incubating; enables a build-time check
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
public @interface BytecodeInterfacing {
/** Groovy version whose compiler first emitted references to this
entry point. */
String since();
/** Groovy version that stopped emitting references; "" = still emitted.
Set this when the compiler no longer generates the reference
but old bytecode still needs it. */
String obsoleteSince() default "";
/** Earliest Groovy version in which this may be removed (once min
supported bytecode passes obsoleteSince). */
String removableIn() default "";
/** Frozen JVM method descriptor, so a test can assert the live
signature never drifts. */
String descriptor() default "";
}
Why these choices:
- Where: org.apache.groovy.lang.annotation, alongside @Incubating —
that's the established home for framework-level (non-transform)
annotations.
- The version range is exactly Jochen's "introduced → no longer used
but still maintained." since + obsoleteSince captures the live span;
removableIn encodes the cleanup trigger so it's not lost. This
directly maps onto reality: staticArrayAccess would be since="2.5.0",
obsoleteSince="6.0.0"; the v7 entry points since="2.5.0",
obsoleteSince="3.0.0"; bootstrap since="2.1.0" with no obsolete date.
- RUNTIME + descriptor() unlocks the real payoff over a doc comment:
a tiny test (or extend the existing binary-compatibility subproject)
can reflect over all @BytecodeInterfacing members and assert the
descriptor still matches — so an accidental signature change to a
frozen method fails
CI, while everything not annotated is understood to be freely
mutable. That's the enforcement @Internal can't give you.
- It's orthogonal to @Internal/@Deprecated: a method can be
@Internal (don't call from source) and @BytecodeInterfacing (but the
signature is frozen for old class files). @Deprecated is wrong for the
live bootstrap — it's not deprecated, it's permanent-by-contract;
conversely
fromCache/selectMethod are @Deprecated but not @BytecodeInterfacing,
which instantly tells you they're removable. That contrast is the
whole argument for a distinct annotation.
On Wed, Jun 17, 2026 at 8:46 AM Jochen Theodorou <[email protected]> wrote:
>
> Actually I have one question...
>
> Can we make all the indy callsite code, including IndyInterface
> internal, to allow us to freely change the public method signatures?
> Plus I was thinking that maybe we should add another such annotation,
> that we could call BytecodeInterfacing or something like that, to mark a
> method as directly called from compiled bytecode. Even better with a
> version range for when it was introduced to when it was no longer used,
> but still needs to be maintained to support older Groovy versions.
>
> bye Jochen
>
> On 6/16/26 14:53, Paul King wrote:
> > Hi folks,
> >
> > I have just a few things to tidy up (maybe a day or two) then I plan
> > to do releases 5.0.7 and 6.0.0-alpha-2.
> >
> > Let me know if you are working on something that should be included or
> > anything which we should wait for.
> >
> > Thanks, Paul.
>