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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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? >>
