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

Steffen Yount steffenyount at gmail.com
Wed Jan 28 00:28:40 UTC 2026


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:

   1. *Tier 1: Type Specialization:* Most specific generic match wins,
   applying the same scrutiny and rules currently used for existing static
   overloaded method resolution.
   2. *Tier 2: Physical Locality:* Provider in the same file (.jar/.class)
   as the caller wins.
   3. *Tier 3: Loader Proximity:* Nearest ClassLoader in the delegation
   path wins.
   4. *Tier 4: Modular Topology:* Internal > Explicit > java.base >
   Transitive > Automatic.
   5. *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/20260127/68a650ed/attachment.htm>


More information about the amber-dev mailing list