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