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?

Reply via email to