Re: Proposal: Static Service Traits—Enhancing Java’s Static Polymorphism and ServiceLoader Facilities

Josiah Noel josiahnoel at gmail.com
Thu Jan 29 12:54:11 UTC 2026


Can't you write an annotation processor to do all three of those checks
during compilation?

On Thu, Jan 29, 2026, 2:01 AM Olexandr Rotan <rotanolexandr842 at gmail.com>
wrote:

> I may be not exactly at the same topic as Steffen, but I think that, more
> vaguely speaking, the issue can be more generally described as "lack of
> compile time metaprogramming for contract verification", which manifests
> in, for example, such scenarios:
>
> 1. No way to verify jackson-using class has exactly one @JsonCreator
> 2. No way to assert there is exactly one static method that is invokable
> with given arguments (static traits specifically, for example ensure there
> is some unambigous metadata-producing method for runtime discovery
> mechanisms)
> 3. Or, more generally, no way to ensure that class A implements a
> word-phrased (on the contrary to method-phrased, which is statically
> enforced) contract of interface B, such as for SequencedCollection. Of
> course, this particular example is largely beyond the limits of
> metaprogramming, but for something simpler such as potential interface
> "UnmodifiableList" that would have declared that all modifying methods of
> List in class implementing UnmodifiableList should ultimately throw an
> exception or at least not mutate backing state fields, static checks are
> doable.
>
> The other thing is that this is what static analysis tools are made for.
> Unfortunately, not everyone uses even developer-provided analysis profiles
> when using said developer`s library, so the fact that there is no langauge
> level contract enforcement capabilities for more complex scenarios surely
> leads to a lot of unnecessary runtime errors.
>
> Though, of course, I admit that what I have described (metaprogrammatic
> contract enforcement) is a far larger scope then just static methods.
> Ultimately, even just static-method-contract enforcement would solve a lot
> of reflection-related issues for me when writing things like runtime
> discovery
>
> On Thu, Jan 29, 2026 at 2:31 AM Ron Pressler <ron.pressler at oracle.com>
> wrote:
>
>> The first two paragraphs boil down to “it’s difficult to work with
>> user-defined numeric types, especially with a tight memory layout”. As you
>> probably know, this is a problem we’re already working on solving.
>>
>> Aside from that, I think the problem you’re reporting is:
>>
>> "The java.util.ServiceLoader ... requires explicit custom code to verify
>> bindings at startup for fail-fast behavior. The API remains an extra
>> invocation hurdle with its lookup and instantiation requirements.”
>>
>> What you’re saying about “right-sizing” the language is absolutely right,
>> but for the JDK team to propose a solution to a problem you’ve identified,
>> the problem first needs to be understood. Assuming that what I quoted is
>> the problem you’ve tried expressing, can you elaborate more on:
>>
>> 1. Requires explicit custom code to verify bindings at startup for
>> fail-fast behavior.
>>
>> 2. The API remains an extra invocation hurdle with its lookup and
>> instantiation requirements.
>>
>> The more concrete you are, as in “I tried to do X and this is the code I
>> wrote”, the easier it would be to understand.
>> I think I know what you mean in #1, but it would be helpful to show what
>> code you had to write. In #2, what is exactly the hurdle? E.g. let’s say
>> that the hurdle is that the API is verbose. That is only a problem if it
>> has to be used lots of times in a program, so what was your program that
>> required so many uses of ServiceLoader?
>>
>> (P.S. “right-sizing” the language doesn’t mean that anything that could
>> be improved with a language feature should be because every addition to the
>> language complicates it. We like it when a non-trivial language feature can
>> solve a big problem or multiple small ones.)
>>
>> — Ron
>>
>>
>> > On 28 Jan 2026, at 23:20, Steffen Yount <steffenyount at gmail.com> wrote:
>> >
>> > Hi Ron,
>> >
>> > Thank you for the feedback. It's totally reasonable to push for the
>> > "why" before getting into the "how."
>> >
>> > The personal problem I encounter is the severe and inconvenient
>> > extensibility costs of the current language model. When pursuing
>> > data-oriented design or domain-specific numeric types in Java, I find
>> > the language's current facilities to be a significant design obstacle.
>> > These are not just issues I find while building, but constraints that
>> > fundamentally alter how I consider a project's architecture before a
>> > single line of code is written.
>> >
>> > I have encountered the following specific friction points:
>> >
>> > The Instantiation Tax: On several occasions, I have turned to Java for
>> > intensive math calculations. These efforts typically start small but
>> > eventually outgrow Java's supported numeric abstractions. To move
>> > beyond small memory footprints and 64-bit representational limits, I
>> > find I must refactor to contiguous arrays, reusable Flyweight objects,
>> > and custom math methods just to manage the memory pressure and data
>> > type limitations. Because I cannot define polymorphic static contracts
>> > for these types, I am forced to pay an Instantiation Tax—maintaining
>> > "witness" objects just to access static logic. The ergonomic noise and
>> > heap-inefficiency of the unwanted object headers are so high that I am
>> > often discouraged from pursuing my original abstractions entirely.
>> > While I have used primitive long types as witnesses for static
>> > overloaded method binding, using the NewType pattern for type-safe
>> > bindings remains prohibitively expensive; a new NewType class's
>> > implementation immediately excludes it from participation within the
>> > language's built-in expression operators. In the aforementioned
>> > efforts, I have ultimately abandoned Java for C/C++ simply because
>> > they allowed me to shape the data layout and its static behavior
>> > without this "abstraction tax."
>> >
>> > The Expression Problem (Post-hoc Abstraction): When I find it
>> > necessary to treat third-party classes as part of a common
>> > abstraction—for instance, when attempting to resolve Guice-based
>> > injection or AOP design issues without the "magic" of runtime
>> > reflection—the traditional path is the Adapter Pattern. I find this
>> > route unsustainable; creating a new wrapper class for every instance
>> > not only fragments object identity but generates significant GC churn.
>> > I have seen production code with wrapper classes nested eight levels
>> > deep just to satisfy disparate abstractions. The ability to implement
>> > type level contracts rather than just instance level contracts, along
>> > with type level extension methods, would allow us to side-step the
>> > wrapper classes with implementations that bind to existing types
>> > without modifying their source code. The lack of them serves as a wall
>> > preventing me from designing the clean, type-safe, and AOT-friendly
>> > systems I know are possible elsewhere.
>> >
>> > The ServiceLoader Ceremony: The java.util.ServiceLoader acts like more
>> > of a library than a language feature. It requires explicit custom code
>> > to verify bindings at startup for fail-fast behavior. The API remains
>> > an extra invocation hurdle with its lookup and instantiation
>> > requirements. A coherent language-integrated, static service interface
>> > method dispatch and binding would dramatically reduce this ceremony
>> > and increase utility by moving it from a manual runtime search to a
>> > link-time certainty.
>> >
>> > My "big picture" problem is that Java’s evolution model currently
>> > makes it difficult to "grow the language" via libraries that feel
>> > native and are performan, such as the recent prototype exploration of
>> > Float16. I believe the language should provide the infrastructure for
>> > _Static Service Traits_ or otherwise make that kind of library-driven
>> > growth a standard capability for all developers.
>> >
>> > I feel "corralled" into 1990s instance-based OOP. When I explore
>> > data-oriented design or high-performance numeric abstractions, the
>> > features found in my competitors' language tool belts would be
>> > incredibly useful; without them, I find myself looking at alternate
>> > language implementations just to avoid Java's structural obstacles.
>> >
>> > Given that Project Amber’s stated mission is to "right-size language
>> > ceremony" and improve developer productivity, doesn't a proposal that
>> > eliminates this Instantiation Tax and link-time service ceremony seem
>> > like a relevant and worthy pursuit?
>> >
>> > -Steffen
>> >
>> >
>> > On Wed, Jan 28, 2026 at 7:45 AM Ron Pressler <ron.pressler at oracle.com>
>> wrote:
>> >>
>> >> The hardest part in designing and evolving a language is deciding
>> which problems are important enough to merit a solution in the language and
>> how their priorities compares to other problems. It’s the hardest part
>> because the language team are expert at coming up with solutions, but they
>> may not always know what problems people enoucnter in the field, how
>> frequently they encounter them, and how they work around them today.
>> >>
>> >> I’m sure there is some problem hidden here and in your previous post,
>> but it is not articulated well and is hidden in a poposed solution, even
>> though no solution is even worth exploring before understanding the
>> problem. And so the best way to get to a solution is for you to focus on
>> the problem and only on the problem.
>> >>
>> >> What was the problem you *personally* ran into? How bad were its
>> implications? How did you work around it?
>> >>
>> >> With the hard part done, the JDK team will then be able to assess its
>> severity and think whether it merits a solution in the JDK, if so, where
>> (language, libraries, or VM), and how to prioritise it against other
>> problems worth tackling. Then they’ll be able to propose a solution, and
>> that’s would be the time to try it out and discuss it.
>> >>
>> >> — Ron
>> >>
>> >>
>> >>
>> >>> On 28 Jan 2026, at 00:28, Steffen Yount <steffenyount at gmail.com>
>> wrote:
>> >>>
>> >>> The recent thread "Java Language Enhancement: Disallow access to
>> static members via object references" highlights a long-standing tension in
>> Java's handling of static members. While that thread seeks to further
>> decouple instance state from static logic, I would like to propose moving
>> in the opposite direction: "doubling down" on Java’s compile-time and
>> link-time static polymorphism.
>> >>>
>> >>> By beefing up java.util.ServiceLoader facilities and integrating its
>> discovery mechanism directly into the language via Static Service Traits,
>> we can facilitate the "Witness Object" paradigm discussed by Brian Goetz's
>> "growing the java language" presentation and the algebraic "well-known
>> interface" model for custom numeric types (like Float16) proposed  in Joe
>> Darcy's "Paths to Support Additional Numeric Types on the Java Platform"
>> presentation.
>> >>>
>> >>> == Static Service Traits for Java ==
>> >>>
>> >>> I propose a system of Static Service Traits. I use the term "Trait"
>> advisedly: this feature adopts a rigorous Coherence Model (inspired by
>> systems like Rust) to ensure that service resolution is not merely a
>> dynamic search, but a type-safe, deterministic binding of static
>> capabilities to types.
>> >>> 1. The service Contextual Keyword
>> >>> We introduce service as a contextual modifier for interface
>> declarations. Marking an interface as a service identifies it as a "service
>> type" with a contract for static capabilities and a high-performance
>> service provider registry.
>> >>>
>> >>> 2. Static Implementations and Extension Methods
>> >>>    • Static Implementations:
>> >>>        • In Interface Headers: interface MyTrait implements
>> ServiceX<T>. Methods are fulfilled as static.
>> >>>        • In Class Headers: class MyClass implements static
>> Numeric<Float16>. Methods are implemented as static on the class. Existing
>> signature rules prevent a method from being both a static and an instance
>> implementation simultaneously.
>> >>>    • Static Extension Methods: Desugared at the call site.
>> myInstance.method() becomes MyClass.method(myInstance). Notably, if
>> myInstance is null, it desugars to MyClass.method(null) without an
>> immediate NullPointerException.
>> >>>    • Ergonomic Aliases: To simplify signatures, we introduce private
>> nested static type aliases This and Super (e.g., static This add(This a,
>> This b)).
>> >>>
>> >>> 3. Operational Mechanics & Link-Time Integration
>> >>> A ServiceLoader Controller is integrated into the JVM’s class-loading
>> pipeline. During class definition, the Controller eagerly extracts all
>> relevant metadata to populate the Static Service Provider Registry,
>> including:
>> >>>    • Header-level static implements and implements declarations.
>> >>>    • Service binding descriptors from module-info.class.
>> >>>    • META-INF/services/ provider-configuration files.
>> >>> Hierarchical Precedence Resolution: To ensure deterministic binding,
>> the Controller resolves call sites to their most specific service provider
>> via a waterfall dispatch model:
>> >>>    • Tier 1: Type Specialization: Most specific generic match wins,
>> applying the same scrutiny and rules currently used for existing static
>> overloaded method resolution.
>> >>>    • Tier 2: Physical Locality: Provider in the same file
>> (.jar/.class) as the caller wins.
>> >>>    • Tier 3: Loader Proximity: Nearest ClassLoader in the delegation
>> path wins.
>> >>>    • Tier 4: Modular Topology: Internal > Explicit > java.base >
>> Transitive > Automatic.
>> >>>    • Tier 5: Sequential Order: Final tie-breaker via Classpath order.
>> >>>
>> >>> 4. Coherence, The Orphan Rule, and Quarantining
>> >>> To achieve the type-safety of a trait system, we enforce an adapted
>> Orphan Rule: A module (or package on the classpath) must own either the
>> service interface or the target type to define an implementation.
>> >>>    • Coherence Enforcement: Violations in modular code trigger a
>> LinkageError.
>> >>>    • Behavioral Continuity: Violations in classpath code trigger a
>> load-time warning and the provider is quarantined from the Static Registry.
>> To ensure continuity, quarantined providers remain accessible via existing
>> java.util.ServiceLoader API calls, protecting legacy iteration-based
>> discovery while ensuring the integrity of the new link-time dispatch.
>> >>> 5. Performance and AOT Considerations
>> >>> This model transforms ServiceLoader into a link-time resolver. JIT
>> compilers can treat service calls as direct invokestatic instructions,
>> enabling aggressive optimization. This is highly compatible with Project
>> Leyden and GraalVM, as precedence can be "baked" into the binary during AOT
>> compilation.
>> >>> Conclusion
>> >>> By transitioning ServiceLoader to a link-time resolver, we provide a
>> type-safe, high-performance path for algebraic types and witness-based
>> generics. This allows Java to "grow" through libraries—fulfilling the goals
>> of both Darcy and Goetz—while maintaining the performance and stability
>> characteristics of the modern JVM.
>> >>>
>> >>>
>> >>> Thoughts?
>> >>
>>
>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20260129/3d50a810/attachment-0001.htm>


More information about the amber-dev mailing list