From develop4lasu at gmail.com Sun Feb 1 15:33:56 2026 From: develop4lasu at gmail.com (=?UTF-8?Q?Marek_Kozie=C5=82?=) Date: Sun, 1 Feb 2026 16:33:56 +0100 Subject: Discussion: The Glue Classes - Explicit, Modular Type Extension Message-ID: Hi, As I had time to fill missing elements from Glue Classes proposal - I would be thankful for any feedback or pointers to alternative solutions. To avoid any doubt, I?ve also included the reasoning and the steps that led me to this design. I?ve tried to keep the formatting readable. If it isn?t clear enough, you can also read it at: https://lasu2string.blogspot.com/2026/01/Glue-classes-for-Java.html Sample code: | Objects.requireNonNull(some); | final A a = some.getA(); | Objects.requireNonNull(a, "explanation"); | validate(a); | final B b = a.getB(); | Objects.requireNonNull(b, "explanation"); | b.process(param1); Same code written using glue: | some..ensureNotNull() | .getA()..ensureNotNull("explanation")..ensureValid() | .getB()..ensureNotNull("explanation")..process(param1..ensureNotNull()); MOTIVATION We made great success moving to OO, sadly we are only quarter of the road there. Why Glue Beat Static Utilities in Real-World Codebases: While many of the core benefits of glue classes (such as method attachment, composability, and disciplined null handling) could be simulated with static utility methods - the practical outcome is fundamentally different and hard to compare with glue classes. Experience with External Libraries Most code bases I've used (and contributed to) are full of sprawling utility modules, overloaded with: - Configurable options and sys-opts - Redundant null-checking and validation - A continuous tension between usability and maximum usable decomposition This leads to code that is either unreadably cryptic or so decomposed that developers struggle to discover, connect, or reason about intent. Static Methods: Limitations Decomposition makes compact APIs hard to discover: Static methods live outside the type they operate on. Even with clever tags or code-rewriting, you can't naturally find, autocomplete, or chain them as you would with instance (or "glue") methods. Responsibility separation increases friction: The more you split up code as recommended ("don't pollute domain objects, keep util logic separate"), the less obvious it is to the next developer where to look for required behaviour. Null-handling becomes boilerplate: The majority of library util methods guard against null upfront?resulting in repeated validation, fat method signatures, or the spread of verbose Optional-driven patterns(force us to rewrite known code just for null from time to time). Why Glue Classes Are Fruitful Discoverability and fluency: By attaching methods as views directly to types, glue classes make contextually appropriate utilities instantly available and visible right where they're needed. Controlled extension and evolution: Behavioural changes, versioning, and testing remain isolated and explicit; you can swap, layer, or upgrade glue classes without altering core types or writing brittle adapters, I would compare it to default methods that are not limited by class ownership and not-null state. Centralized, composable null policies: You can bake robust, contextual null-handling exactly once, in glue, and chain safely- even for null receivers. This way code could be decomposed without negative consequences. Cleaner architecture without trade-off: Code remains decomposed, modular, and maintainable, yet the surface API is obvious - giving the best of both worlds. Summary While static utilities, annotations, and dynamic tooling can go a long way to simulate some extension patterns, only glue classes offer a truly fruitful, disciplined, and developer-friendly solution for modular extension, composable integration, and safe evolution?unlocking readable, discoverable, and maintainable APIs at scale that would work for multiple disconnected teams - basically you can see it as Kotlin?s extension functions on steroids. DERIVATION OF THE PROPOSAL If you?re familiar with Kotlin Extension Functions (since 2011) or C# Extension Methods (since 2007), you know these features let us add methods to existing types, even if we don't own the source code. However, these features suffer from important limitations: C# - Member methods always win C# - Same name among extension will cause compiler error Kotlin - Member functions always win Kotlin - Same name among extension you get a compiler ambiguity or you can rename it in import You can?t distingush member vs extension syntactically, so you also can?t tell which ones could be written to accept null. Because "member always wins", adding a new member later can silently change which function gets called, including changing null behavior. They allow discoverability from IDE viewpoint - but they scale with each method. Those limitations makes them as much a burden as a cure. In 2009 I tried bring up concept that had none of those problems. It's under: 2009-March (Glue classes proposal 0.9) - https://mail.openjdk.org/pipermail/coin-dev/2009-March/thread.html#:~:text=0.9 - because of it's rudimentally form I don't recommend reading it just yet. Non-Java Below you will finding Non-Java syntax that is not particularly necessary and could be changed to other form, but was introduced to make understanding of new concept more intuitive: .. - static member selection operator G[] this - this used as variable name public static void glue(Iterator this) - sample anchor method for IDE extends ..GA, ..GB - multi-inheritance of static methods The basic idea Glue classes are special classes that allow us to add methods to any classes, records or types, potentially value classes, as though we were "gluing" new methods onto them. Unlike extension functions, they?re designed to be systematically discoverable. Instead of manually searching or importing functions, the IDE could automatically know which glue classes can apply. They can use natural class hierarchies to avoid name collisions. Lets follow a practical example how this concept would change Java the one where we supposedly want to add methods to any array: | public class ArrayGlue{ | // this method would carry metadata for discovery | public final static void glue(G[] this){ /* empty */ } | | // -1 in extractByIndexes uses null | public static G[] extractByIndexes(G[] this, int... indexes) { /* ... */ } | | public static G[] setOn(G[] this, int index, G value) { /* ... */ } | } Usage: | String [] record = ...; | String [] identification = ArrayGlue.extractByIndexes(record, 0, 2, 3, -1); | ArrayGlue.setOn(identification, 3, param1); With a static import, you can write: | import static ArrayGlue.*: | | String [] record = ...; | String [] identification = extractByIndexes(record, 0, 2, 3, -1); | setOn(identification, 3, param1); If we introduce a Dot-dot operator ".." that work similar to ".": - when used at start it's ensures that only imported static methods are considered while matching methods - when used as connection it injects the variable or expresion that is on the left as the first parameter First we would get secure against possibility of new methods with same name comming to live: import static ArrayGlue.*: | String [] record = ...; | String [] identification = ..extractByIndexes(record, 0, 2, 3, -1); | ..setOn(identification, 3, param1); and then we can tranform it to: | import static ArrayGlue.*: | | String [] record = ...; | String [] identification = record..extractByIndexes(0, 2, 3, -1); | identification..setOn(3, param1); and in it's core it can be considered as tranformation/sugar that require more work from IDE and compiler. This could be further shortened to: import static ArrayGlue.*: | String [] record = ...; | String [] identification | = record | ..extractByIndexes(0, 2, 3, -1) | ..setOn(3, param1); giving us nice fluent api that is incomparably easier to read and can gracefully handle null-s as well. This could be considered an alternative to: Java Language Enhancement: Disallow access to static members via object references Discoverability Now lets focus on: | public static void glue(G[] this){ /* empty */ } For every Glue class, the IDE only needs to try GlueClass.glue(var) to check applicability. If it fits, its extension methods are available, and this can be efficiently cached. Discoverability differs from extension methods in this regards that it have much lower change to fail, because it's based on much lower amount of parameters. Ina same regard it's easier to wtite class that already have one or two generic arguments and then 0 ~ 2 in the method instead of mashing them together in static method - same concept apply partially here. Glue classes as any other can be extended to create more complex utilities: | public class ArrayFastGlue extends ArrayGlue { // or ..ArrayGlue | public static void glue(G[] this){ /* empty */ } | | public static G[] extractByIndexes(G[] this, int... indexes) { /* ... */ } | } For a "glue" method (the other static methods in same class) to work: The method's generic type parameter(s) at the start, and the first parameter (the receiver), must match the glue method?s signature for correct association, type safety, and discoverability in IDEs. To demonstrate more complex example lets extend Iterator and add method that would allow map elements with given funtion: | public class IteratorGlue { | public static void glue(Iterator this){} | | public static Iterator map(Iterator this, Function fn) { | // Implementation: returns an Iterator that applies fn to each element of original Iterator | } | } and usage would be: | Iterator it = ...; | Iterator numbers = it..map(String::length); Name collisions Following Java namespace ruless we could be more precise and use longer names: | Iterator numbers = it..IteratorGlue.map(String::length); or if needed fully qualified name | Iterator numbers = it..package.IteratorGlue.map(String::length); This would match current Java logic where each syntax is valid: | map(it, String::length); | IteratorGlue.map(it, String::length); | package.IteratorGlue.map(it, String::length); Extending For adding new methods to exisintg ones we could use simple inheritance: | public class ArrayFastGlue extends ..ArrayGlue{ | | public static G[] extractByIndexes(G[] this, int... indexes) { /* ... */ } | | } This approach can preferably allow for discovery to omit glue classes that are already extended.Multiple inheritance What's more, exclusively for glue classes, we could allow multiple inheritance restricted to static-only methods - further increasing flexibility and quality. The following rules would apply: If two (or more) parent glue classes define static methods with the same signature, the child glue class MUST explicitly re-declare these methods (with the option to delegate to a parent method if desired). Only the most-derived (child/last) glue class in a given inheritance hierarchy is discoverable for extension methods. When a variable is checked for glue extension, the IDE (or compiler) only considers the last glue class in the hierarchy. Methods inherited from parent glue classes are available only if they have not been redeclared (overridden) in the child class. This both prevents method ambiguity and ensures intentional API design. | public class ArrayComplexGlue extends ..ArrayFastGlue, ..ArrayApacheGlue{ | | public static void glue(G[] this){ /* empty */ } // need to be restated to eliminate collision | | public static G[] collision(G[] this){ | return this..ArrayApacheGlue.collision(); | } | | // Marking already existing method as depeciated | @Deprecated | public static G[] unefficient(G[] this){ | return this..ArrayApacheGlue.unefficient(); | } | | } This approach can preferably allow for discovery to omit glue classes that are already extended.Spaces This would make ideal solution for everyday use, but it would still make the classes that are globally used cluttered or force developers to use really bad names to prevent collisions - to solve this problem we could add custom domain spaces (mutable, immutable, efficient, secure, archaic, integration, ... or self-domain like o for object ): To make this happen we would need to exclude classes that start with lowercase character from import and exploration by default (probably for glue classes only) or make it at default skip inner/sub glue classes (if written then it would still work); This way if we want to extend class with methods and we can categorise them by spaces then we have really pretty alternative to bad naming convention: | public class ArrayGlue{ | public final static void glue(G[] this){ /* empty */ } | | public static G[] setOn(G[] this, int index, G value) { /* ... */ } | | public static G[] copyWithSetOn(G[] this, int index, G value) { /* ... */ } | } could be re categorized to mutable and immutable spaces: | public class ArrayGlue{ | public final static void glue(G[] this){ /* empty */ } | | public static class immutable{ | public final static void glue(G[] this){ /* empty */ } | | public static G[] setOn(G[] this, int index, G value) { /* copy is made */ } | } | | public static class mutable{ | public final static void glue(G[] this){ /* empty */ } | | public static G[] setOn(G[] this, int index, G value) { /* given array is used */ } | } | | } this way code: | String[] record = ...; | record = record..copyWithSetOn(1, "~"); could be rewritten to: | String[] record = ...; | record = record..immutable.setOn(1, "~"); Import caveat: import static pkg.ArrayGlue.*; should contrary to current compiler behaviour import subspace glue clases. This would be deliberate incompatible with current Java. | import glue pkg.ArrayGlue; that would be resolved to proper imports: | import pkg.ArrayGlue; | import static pkg.ArrayGlue.*; | import static pkg.ArrayGlue.immutable; | import static pkg.ArrayGlue.mutable; - to make behaviour consistent with glue class purpose. OR glue subclasses should be treated as regular members and become available through standard imports, without any additional semantic transformations - this would be the better option in my opinion! Limiter: Resolving glue methods requires a resolution limiter. After transforming | record..copyWithSetOn(1, "~"); into | ..copyWithSetOn(record, 1, "~"); // .. is here 'only static methods filter' the compiler must not consider instance methods named copyWithSetOn. Resolution for calls originating from .. must be restricted to static methods only effectively forcing compiler to skip one step.Compilation vs Discoverability (IDE): Same as it's now discoverability would be handled by IDE and compilation would be determined by import. What IDEs Do Now (Standard Java) When you type ., the IDE: Looks up the static type at the cursor location. Fetches all visible methods from the class, all its superclasses, and all implemented interfaces. Maybe also adds static imports, inherited generic methods, and overrides. This process is fast because: The class/method hierarchy is well-known, fixed, and heavily indexed/cached by the IDE. There are relatively few methods per type (typically in the dozens, rarely more than a few hundred even in very complex hierarchies). Similar process would be required for glue classes as well. The IDE would need to build an indexes: GlueIndex[] - for certain match InterfaceA -> [GlueA, GlueB, ...] ArrayList -> [GlueC] Map -> [GlueMapUtils, ...] Object -> [ObjectGlue] ... PotentialGlueIndex[] - for potential match InterfaceA -> [InterfaceAGlue, ...] ... one more if we allow more complex syntax/li> For common completions: User types foo., IDE additionally to classic completions gets the static type of foo. Looks up direct match in glue indexes. Optionally traverses the inheritance/superinterface tree. Apply filtering if needed Quickly gets all matching glue methods for suggestion. Sample placement in index ? extends Foo >> GlueIndex[Foo] ? super Foo >> at the top level should not be allowed as it do not give any particullar usability or could be placed in GlueIndex[Object] G extends InterfaceA & InterfaceB >> GlueIndex[InterfaceA] or GlueIndex[InterfaceB] Clarifications: A wildcard bound like ? extends that appears inside a generic type is recorded only as a constraint used later during filtering, not as the primary key in the index. A receiver parameter declared as ? extends InterfaceA is indexed under InterfaceA. For a type parameter declared as T extends InterfaceA & InterfaceB, it does not matter which of the interfaces is used as the primary indexing key, because any valid T must implement both. Discovery based on one bound will still find the glue, and a subsequent filtering step will verify that the second bound is also satisfied. Glue classes inherit the same erasure limitations static methods already have today. Discovery is based on one type vs all method signature - and it's limiting factor as well. Practical performance: Only a handful of glues per common type. Fast code completion: Indexed lookups are fast; filtering is cheap for non-complex hierarchies. Scalable for project or module scope: The cost of glue-based completion/discovery grows linearly in the number of glue classes that are applicable to the type in question. In other words: For a given type G, if there are k glue classes that apply to G, then lookup is O(k). Adding one more glue for G turns this into O(k+1); so the complexity grows proportionally with the number of glues relevant to G, not with the total size of the project or classpath. Futher more with effort we could limit it to O(1) IDE can provide discoverability: You could even have a "show all glues for this type" menu. When finding name collision IDE could suggest qualified names as well: > ..ensureNotNull(); // ObjectGlue > ..call(); // FooMod1Glue > ..FooMod1Glue.call(); > ..call(); // FooMod2Glue > ..FooMod2Glue.call(); Collisions between independent glue classes: // Library X | public class XArrayGlue { | public static void glue(G[] this) {} | public static G[] map(G[] this, Function fn) { ... } | } // Library Y | public class YArrayGlue { | public static void glue(G[] this) {} | public static G[] map(G[] this, Function fn) { ... } | } | import XArrayGlue; | import XArrayGlue.*; | | arr..map(...); // OK because only XArrayGlue is visible for compiler | import XArrayGlue; | import XArrayGlue.*; | import YArrayGlue; | import YArrayGlue.*; | | arr..map(...) // ERROR: ambiguity | arr..XArrayGlue.map(...) // OK | arr..YArrayGlue.map(...) // OK What's more they can make a lot of other desirable changes unnecesary (Elvis operator, Null-Safe operator and many more), as static methods do not limit us to not-null variables, they would bo no as compact but at the same time they would give freedom of composing logic. PARTIAL GENERICS The lack of partial generics types parameters inferencing should be solved for quality of glue classes - this not stictly necessary and could be considered it's own feature. Java can only get all in or all out, while it should be possible to selectively infer generic types, this way, the one of many that we actualy want different or compiler could not infer could be specified. Bad but usefull example: | public static Map listToMapFrame(List keys) {...} calling this method would always require giveing both paramteres / but in most cases only second one is needed, so lets use ?? are marker for compiler to infer parameter. So instead of: | Map m = Maps. listToMapFrame(List.of("a", "b", "c")); we could have: | Map m = Maps. listToMapFrame(List.of("a", "b", "c")); In itself this is not much / but with glue methods this would help a lot, this way glue part of generic arguments could be left to compiler making syntacs easier to work with. | public class IteratorGlue { | public static void glue(Iterator this){} | | public static Iterator map(Iterator this, Function fn) { | // Implementation: returns an Iterator that applies fn to each element of original Iterator | } | } | Iterator it = ...; | Iterator numbers = it..package.IteratorGlue.map(String::length); so when needed we would be able to write/ just as now we are not required (in most cases) to redeclare class generic types: Iterator numbers = it..package.IteratorGlue.map(String::length); // under glue we would have <[G,] R> so both and full could be used decoded to: | Iterator numbers = ..package.IteratorGlue.map(it, String::length); instead of: | Iterator numbers = it..package.IteratorGlue.map(String::length); So when fewer type arguments are provided than the method declares, the missing leading type arguments are treated as ?? (to be inferred), so on a method is interpreted as . LAST TOUCHES With all this we would be at really good position, but in same time new code will clash when mixed with classic methods calls. It would still work as we can always ensure security: ..glueMthods()..ensureNotNull().classicMethods(); Still there is one more path to be taken - consider classic classes as self-glue in witch case each method could be in same time compiled to classic one and glue without unnecessary code duplication (final shape is up to debate). | class DTO{ | private String name; | | public glue void setName(String name){ | if (this==null){ return; } | this.name = name; | } | | public glue String getName(){ | if (this==null){ return null; } | return this.name; | } | } For this reason | if (this == null) { return; } would be as the same time : - this - is a conceptual receiver parameter for glue method - this is never null at runtime, so this == null and this != null is dead code and can be removed/optimized by the compiler/JIT. - it's exact reason why this is used in glue samples FINAL STEP As a final step we could merge glue method with class signature, transforimg: | public class ArrayGlue{ | // this method would carry metadata for discovery | public final static void glue(G[] this){ /* empty */ } | | // -1 in extractByIndexes uses null | public static G[] extractByIndexes(G[] this, int... indexes) { /* ... */ } | | public static G[] setOn(G[] this, int index, G value) { /* ... */ } | } into (under the hood it could be still glue-method): | public glue class ArrayGlue glue(G[]){ | | // -1 in extractByIndexes uses null | public static G[] extractByIndexes(int... indexes) { /* ... */ } | | public static G[] setOn(int index, G value) { /* ... */ } | } making glue classes almost same classic one. OVERVIEW FEATURE SUMMARY: Glue classes(methods) introduce a language-level mechanism allowing developers to add methods to existing types without modifying their source code, breaking encapsulation, or incurring runtime overhead. Using '..' to call static methods. Glue classes provide type-safe, modular, and discoverable extension capabilities, formalizing patterns typically handled by utility classes, extension methods, or reflection-based frameworks. At the same time inner Glue classes & methods would allow to keep gains where private access is needed. Bindings: The .. operator binds identically to the . operator in terms of precedence and associativity(except excluding members), but differs in semantics: the left-hand expression is evaluated once and passed as first argument, and instead routes the receiver value to a statically resolved glue method. Discoverability:All glue methods applicable to a type are always visible for discover. For compilation import are required making glue methods match deterministic. Attach methods to any class, interface, array, or type parameter explicitly. Access follow standard Java rules. Fully static, compile-time resolution: No runtime cost, reflection, proxies, or bytecode tricks. Inheritance-based conflict resolution: Only imported glue classes are available for compilation. If both base and derived glue classes are imported(unnecessary), the derived (subclass) glue will take precedence. Explicit import and qualification: Only imported glue classes are available for resolution, preventing accidental API pollution. Invocable on null receivers: Glue methods can be designed to handle null, enabling centralized and fluent null-handling. Module and JPMS friendly: Glue classes fit into Java?s module system, enforcing clean integration and export boundaries. MAJOR ADVANTAGE: Clean, type-safe, and modular extension of existing APIs! Glue classes solve the problem of utility/god classes, domain pollution, and ambiguous extension by enabling explicit, discoverable, and testable augmentation of types without touching their source or relying on runtime mechanisms. MAJOR BENEFITS: Separation of Core and Utility Logic: Keeps domain classes clean; convenience, formatting, mapping, and integration logic are moved to glue classes. First-Class Integration and Mapping: Supports explicit, safe cross-module converters and bridges without inheritance tangles or reflection frameworks. Better Discoverability and Readability: Glue methods appear as instance-like methods in IDEs, making APIs easier to learn, read, and maintain. Centralized, Fluent Null Handling: Glue can define null policies; methods can be called on null receivers, supporting robust pipeline-style code. Safe API Evolution and Versioning: Allows new glue versions to coexist; inheritance and import resolve conflicts clearly and compile-time safely. Testing Isolation: Glue methods are stateless and separate, making isolated testing easier and less error-prone. Architectural Clarity: APIs and modules are kept clean; glue methods are never accidentally leaked across modules. Ability to Override or Replace Legacy Utility Methods: Glue classes allow to fix, optimize, mark as deprecated, override, fully replace outdated, unsafe, or redundant utility methods without changing the original utility class or domain model. This also enables remediation of API debt and architectural inconsistencies in external libraries or legacy modules - all through explicit, compile-time safe glue mechanisms, not invasive source edits or risky runtime hooks. Generated Classes & Records & value classes: Natural targets for glue class extensions. Lambdas: Glue chaining is fully compatible with lambdas, example: | values..iterate() // | ..filterEach(t -> t != null) // | ..mapEach(t -> t < 3 ? -t : t) // | ..collectToArray(Integer[].class); MAJOR DISADVANTAGE: Requires language, compiler, and IDE changes. New syntax and type resolution rules: Developers must learn and adapt to glue concepts. More complex collision resolution than standard utilities or extensions. ALTERNATIVES: Static utility/helper classes (e.g., *Utils, *Helper): Frequent in Java, often lead to "God" classes and poor discoverability. Extension methods (C#, Kotlin): Partial coverage; limited discoverability, poor collision/versioning management, little module clarity. Reflection-based frameworks and bytecode weaving: Dynamic but fragile; runtime overhead, ambiguity, hard to reason about and test. Optional wrappers and null-patterns: Verbose, contagious throughout codebase, break fluent APIs, often make code bloated. EXAMPLES Classic - doing code review (you can find one minor error) of this AI-generated code is recommended before moving forward to same glue version (explanations are moved to end to make it fair): | String[] dict = { /* your string data */ }; | | // Step 1: | List> groups = new ArrayList<>(); | boolean[] used = new boolean[dict.length]; | | for (int i = 0; i < dict.length; i++) { | if (used[i]) continue; | List group = new ArrayList<>(); | char key = dict[i].charAt(0); | for (int j = 0; j < dict.length; j++) { | if (!used[j] && dict[j].charAt(0) == key) { | group.add(dict[j]); | used[j] = true; | } | } | groups.add(group); | } | | // Step 2: | List mappedGroups = new ArrayList<>(); | for (List group : groups) { | int n = group.size(); | if (n <= 1) { | mappedGroups.add(group.toArray(new String[0])); | } else { | String[] arr = new String[4]; | arr[0] = group.get(0); | arr[1] = "~"; | arr[2] = group.get(n-1); | arr[3] = ""; | mappedGroups.add(arr); | } | } | | // Step 3: | StringBuilder builder = new StringBuilder(); | | for (int i = 0; i < mappedGroups.size(); i++) { | String[] arr = mappedGroups.get(i); | if (i > 0) { | builder.append(", "); | } | builder.append(String.join(" ", arr)); } | | // If you want the result as a String: | String printout = builder.toString(); | | // Step 1: Group by first character connection | // Step 2: Map/Transform each group | // Step 3: Flatten Glue version: | String[] dict = ...; | | String printout = dict | ..iterateByConnection((a, b) -> a..firstChar()..equals(b..firstChar())) // | ..map(t -> t..size() <= 1 // | ? t // | : t..extractByIndexes(0, -1, t.length-1, -1)..setOn(1, "~") // | ) // | ..collectToArray(String[].class) // | ..joinArrays(", ", " "); Glue utils: | public class ObjectGlue glue(T) { | public glue boolean equals(this, T other) { /* ... */ } | } | public class StringGlue glue(String) { | public glue Character firstChar(this) { /* ... */ } | } | public class ArrayGlue glue(T[]) { | public glue Iterator iterateByConnection(this, BiPredicate connection) { /* ... */ } | | public glue int size(this) { /* ... */ } | | // -1 in extractByIndexes uses null | public glue T[] extractByIndexes(this, int... indexes) { /* ... */ } | | public glue T[] setOn(this, int index, T value) { /* ... */ } | | public glue String joinArrays(this, String... /* [dimension] */ delimiters) { /* ... */ } | } | public class IteratorGlue glue(Iterator) { | public glue T[] collectToArray(this, Class componentType) { /* ... */ } | | public glue Iterator map(this, Function fn) { /* ... */ } | } Glue to static tranformation: | public class | IteratorGlue glue (Iterator) { | ? ? | public glue < R> Iterator map( this, Function fn) { /* ... */ } = | public static Iterator map( Iterator this, Function fn) | { /* ... */ } | Inner Glue Classes - have same visibility access as inner classes: | public class Outer { | private int value; | | public static class ValueGlue glue(Outer) { | public glue int doubleValue(this) { | return this!=null ? this.value * 2 : 0; // has access to private field of Outer | } | } | } Methods with additional Glue characteristics - allowing for both access methods | public class Storage { | private String name; | | public glue String getName(){ | return this!=null? this.name: null; | } | } // could be transformed to: | public class Storage { | private String name; | | public String getName() { | return this != null ? this.name : null; | } | | public static glue class Glue glue(Storage) { | public glue String getName(this) { | return this != null ? this.name : null; | } | } | } // making both calls possible: | storage.getName(); | storage..getName(); Inheritance of MessageGlue from provided library: | public class MessageGlue glue(Message) { | private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd"); | | public glue String formatDate(this) { | // Not thread safe! Shared FORMAT instance | return FORMAT.format(this.getDate()); | } | } | public class MessageGlueThreadSafe glue(Message) extends MessageGlue { | | @Override | public glue String formatDate(this) { | if (this==null) return null; | // Thread-safe alternative with same format | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); | return this.getDate().toInstant() // | .atZone(ZoneId.systemDefault()) // | .toLocalDate() // | .format(formatter); | } | } Import need to be corrected to MessageGlueThreadSafe (tools could report usage non latest class). Glue Class Hierarchy and Methods: | public glue class ArrayGlue glue(T[]) { | // core glue methods (iterate, size, extractByIndexes, ...) | | public static glue class immutable glue(T[]) { | public glue T[] reversed(this) { /* always creates a new array */ } | public glue T[] setOn(this, int index, T value) { /* copy and set */ } | } | public static glue class mutable glue(T[]) { | public glue void clear(this) { /* ... */ } | public glue String[] reverse(this) { /* ... */ } | public glue T[] setOn(this, int index, T value) { /* in-place set */ } | } | } | public glue class ArrayGlueGen2 extends ArrayGlue glue(T[]) { | public static class immutable extends ArrayGlue.immutable glue(T[]) { | public glue T[] reversed(this) { /* optimized: skip copy for length <= 1 */ } | } | } Standard, Default Import (Preferred) | import ArrayGlue; | | new String[][]{{}}// | ..mutable.reverse() | ..mutable.setOn(3, new String[] {...}) | ..joinArraysToSting(" | ", ","); Importing Newer/Derived Glue (Inheritance/Shadowing) |import ArrayGlue; // WARNING: You are using an older glue, ArrayGlueGen2 is available |import ArrayGlueGen2; | |new String[][]{{}}// | ..mutable.reverse() | ..mutable.setOn(3, new String[] {...}) | ..joinArraysToSting(" | ", ","); Importing Spaces | import ArrayGlue.immutable; // Allowed, but discouraged (warning) | import ArrayGlue.mutable; // Allowed, but discouraged (warning) | import ArrayGlue; // Brings all top-level + inner glue into scope | | new String[][]{{}}// | ..reverse() | ..setOn(3, new String[] {...}); // <-- Compile error: 'setOn' name collision (ambiguous because it come from both immutable and mutable) DETAILS SPECIFICATION: GlueClassDeclaration ClassModifiersopt class Identifier TypeParametersopt glue ( GlueType ) [ extends ..SuperGlueClass (, ..SuperGlueClassB) ] GlueClassBody GlueMethodHeader: MethodModifiersopt glue TypeParameters*opt ResultType GlueMethodDeclarator Throwsopt GlueMethodDeclarator: Identifier ( GlueType this, FormalParameterListopt ) GlueDualMethodDeclarator: // compiled to classic and glue (inside generated InnerGlueClass) Identifier (FormalParameterListopt ) GlueMethodInvocation: variable..glueMethod(...) variable..GlueClass.glueMethod(...) variable..gluePath.GlueClass.glueMethod(...) variable..glueMethod(...) variable..glueMethod(...) GlueDualMethodHeader: MethodModifiersopt glue TypeParametersopt ResultType Identifier ( FormalParameterListopt ) // desugared to an instance method + a static glue method with 'this' inserted COMPILATION: The original type is not physically modified or extended in any way by glue classes; neither instance nor static shape of the type is changed. Rather, when glue methods are invoked, the compiler transforms these calls into static method calls on the relevant glue class, passing the target ("receiver") as the first argument. Additionally, when creating outer glue the original type is preserved as-is in the scope ?no glue methods are added directly ? so any attempt to access protected or private members, or to override internal implementation, is impossible. This guarantees encapsulation, prevents accidental API leakage across modules, and avoids the complexities and hidden bugs of runtime weaving or bytecode patching. All glue logic remains explicit, statically checked, and safely separated, enforcing consistent usage and clear architectural boundaries. At same time inner glue classes follow standard Java ruless and have access to private fields. Summary: Additional metadata may/will be needed by the compiler to resolve glue method linking at compile-time (private method could be used for it). The type is preserved unmodified at the bytecode and source level. Glue usage is enforced at compile-time?all calls are routed via explicit glue classes/methods. Attempting to access glue methods directly on the type (without glue import) will result in a compile-time error. The risk of name collision, encapsulation break, or accidental glue import after removing a glue statement/import - greatly dimished, thereby supporting robust and maintainable code evolution. All glue methods remain usable as classic static methods, providing backwards compatibility and migration flexibility. TESTING: Like simple static methods vs new sematics would give same results. COMPATIBILITY Potentially JARs could be fully compatible with existing ones. Code written using glue classes is only backward compatible; there is no impact on runtime compatibility of older code. CLARIFICATIONS Null Policy for Glue Methods: When invoking a glue method on a potentially-null receiver, the method should by convention check for null and return null directly, skipping further logic. As null-checkings are easily enforced using ..ensureNotNull() when needed - this way there would be no need to create each method twice, one for non-null and the second for null. Conflict/Override Resolution:Glue method name conflicts can be resolved by qualifying the invocation with the GlueClassName, for example, obj..MyGlueClass.method(). If a glue class is extended, standard Java inheritance and import precedence rules apply - meaning that a subclassed glue class can override methods of its predecessor, just as with regular Java static methods. Remember, at the compilation and invocation level, glue methods are just static methods in their glue classes. Thus, all static method resolution rules, import qualifying, and inheritance behaviors apply exactly as they do for ordinary Java static methods. There is no ambiguity introduced by glue; it remains as modular and predictable as standard Java. Methods Alone Could Be Enough: In theory, most glue functionality could be achieved with plain static utility methods that are written to accept the receiver as the first argument (e.g., ensureNotNull(T o)). In principle, even signatures like ensureNotNull(T(this) o) could express this pattern and allow extension-style calls. However, resolving applicable methods for a given type, especially across large codebases, would overwhelm both human readers and IDE tooling. Discoverability, auto-completion, and intent become difficult to manage when extension logic is dispersed across many static methods without any structuring or explicit association to the original type. Glue classes, by contrast, organize extension methods in a type-centric and module-aware way, ensuring that IDEs, analysis tools, and developers can easily locate and reason about available extensions.. Proposed Collision Policy: Glue method name collision: If multiple glue classes provide a method of the same name (e.g. ensureNotNull) for the same target type and both are in scope, it is a compile-time error. POSSIBLE EXCEPTION: If any (or all but one) of the colliding methods are marked as @Deprecated, the non-deprecated method is chosen transparently, or you force users to migrate by requiring they call the non-colliding name. Deprecation + Delegation: You may create a new, better-named method in a new glue class. Old glue class?s method is marked @Deprecated and delegates to the new one or provides upgrade guidance in its javadoc/message. Multiple Inheritance: Ideally we could make glue classes extend multiple glue classes with same glue type. Partial generic type "filling": Filling types need to be complete: public static X method(T t, x) {...} - both X and Y need to be present in calssic static methods or none, but glue classes give us possibility to make only local part filled. Spaces: We could extend import rules to make lower name class-es non importable by default(could be forced) - this way we could create spaces for logic class StringGlues{ public class fast glue(String){ public glue int hash(){...}} } would allow to more precise executions: "SomeString"..fast.hash();. In this case sapce import would be performed by outer glue immport (similar to .*). Migration/Interoperability: IDE and build tools must be glue-aware for this transition to be smooth Expectation: Glue classes/methods inherit all generic features and erasure behavior from static methods. The only addition is the clean, type-centric invocation syntax and modularity. Battle: A modern, well-specified "glue" mechanism would help keep Java the go-to language for large, safe, evolving platforms. Not adopting such a feature likely means falling behind in the expressivity and maintainability race - something other popular languages will quickly capitalize on. Method Reference Syntax: under the hood static code would work the same for method references. Behavioral Consistency: dict..iterateByConnection(...)..map(...)..ifNull(otherMap..ensureNotNull()).toString(); - When chaining expressions where any step may yield null, glue methods such as ifNull(...) allow you to seamlessly fall back to a non-null value, ensuring subsequent method calls can proceed safely and fluently. This enables robust, readable null-handling in extension method pipelines. Chain ambiguity: With long chains, it can be unclear what the current context is. However, glue methods such as ..ensureNotNull() or ..ensureType(Class) make this easy to resolve. If you need to guarantee that a value is not null, simply call ..ensureNotNull(); if you need to validate its type then ..ensureType(Class) clarifies intent directly in the chain. This approach gives you the freedom to make your code as precise and robust as necessary. Additional gain would be avoiding cluttering your scope with temporary variables that are often redundant or, in many cases, difficult to remove from context / sometimes harmful. Dual-method generation: A method declared with glue - inside a normal class is not a normal instance method, but a dual-declared construct that produces: a classic instance method (where this is guaranteed non-null), and a glue static method (where this is an explicit parameter and may be null) This way we can prevent code duplication. Possibility of delegating glue to instance method: It's wrong path as we lose control over null-controll if we would always resolve glue to glueMethod(this){ this!=null ? this.method() : null; } . Recommendation: Use .map() for operations on the whole object or when the collection changes type/form. Use .mapEach() for per-element transformations, making intent clear. Best Practice: Use Context to Guide Null Strategy: ..ensureNotNull(Runnable) - Nulls might translate into user warnings or default field values. - In some cases null are natural. Unclarity: Dual-method support is a possible future evolution only. It does not change how existing, non-glue classes behave in the core proposal. Dual-methods: In case of doubt, the first step should be to analyze the behavior of identical static methods. this vs null: The preferred convention is that glue methods are null-safe by default (they should tolerate a null receiver and handle it gracefully), but this is never enforced. Some glue methods may legitimately require non-null receivers and can assume or enforce non-null where appropriate. Glue class syntax variants: Multiple glue class syntaxes are shown intentionally to illustrate different possible final surface forms and to aid understanding for readers with different backgrounds. They all represent the same underlying concept; a real specification would choose a single concrete syntax. Partial generics (??): The ?? notation is underspecified and not valid Java syntax. It should be treated as a conceptual or future-looking idea, not part of the concrete glue proposal. CONTROL TAKEOVER OPERATOR AS POSSIBLE PATH OF EVOLUTION The glue class system can be efficiently evolved by introducing a control takeover operator, denoted as >>>. This operator enables developers to inject control-flow or observability logic inline within glue method chains, using a concise block syntax such as >>> { ... local ... } where local represents current value in chain.How It Works: At any point in a chain, a developer can insert a control takeover block: | v = value | .stepOne() >>> { if (isLast(local)) { break local; } } // | ..stepTwo() >>> { if (!valid(local)) { return null; } } // | ..stepThree() >>> { log( local ) } // | ..stepFor() ; that could compile to: | L local = null; | localLabel: | { | // Step 1: first glue operation + optional early exit | S step1 = GlueClass.stepOne(value); | if (isLast(local)) { local = step1; break localLabel; }; | | // Step 2: second glue operation + optional return | S step2 = GlueClass.stepTwo(step1); | if (!valid(local)) { return null; } | | // Step 3: final glue operation | S step3 = GlueClass.stepThree(step2); | log(step3); | | S step4 = GlueClass.stepThree(step3); | // Assign result | local = step4; | } Within the block, the special variable local refers to the current value at that position in the chain. The block can be used for: Side effects (logging, metrics, customized debugging) Condition checks (for validation, auditing, or transformation) Control flow takeover Using statements like return ...; - to immediately exit method or break local; to break the chain and return a specific value. Benefits: Readability: Keeps all logic linear and transparent, avoiding deeply nested structures or scattered utility calls. Power: Enables both tap/peek actions and in-chain early exits (short-circuiting), which classic chains in Java cannot naturally perform. Safety & Modularity: Ensures that side-effects and bail-outs are explicit and localized, minimizing risk of bugs and increasing code maintainability. Flexibility for Evolution: Glue chains become even more expressive, supporting robust patterns for validation, recovery, dynamic branching, and auditing - without giving up the clarity of idiomatic Java. Why Base It On { ... local ... }? Using { ... local ... } as the block structure fits perfectly with glue?s philosophy of "extending but not modifying." Developers can operate on local without needing to introduce new variable names or disrupt the data flow. The block?s scope is contained, making side-effects or exit points obvious to reviewers and maintainers. The full write-up also discusses how this feature could help in an era of AI-generated code (clear extension points, better reviewability, etc.), but that is secondary to the core language idea. -- Greetings Marek Kozie? (Lasu) -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Tue Feb 3 00:00:50 2026 From: davidalayachew at gmail.com (David Alayachew) Date: Mon, 2 Feb 2026 19:00:50 -0500 Subject: Question about Array Patterns Message-ID: Hello @amber-dev , I was a little surprised that I didn't notice this until now, but when re-reading Brian's Array Patterns write-up , I realized that my brain auto-completed something that wasn't actually there. In the write-up, there is only the concept of a prefix match or an exact match. But what about a suffix match? Or a "somewhere-in-the-center" match? The original proposal for Array Patterns (that preceded this one) had the "..." syntax, which I mentally auto-completed to be "0 or more elements". Maybe that was a mistake on my part too. Anyways, could I get some clarification? Is that intentionally unspecified for now, pending further thought? Or is that something that will be handled else where? And by all means, what is there, I like. It's just that the message calls itself Array Patterns, so my thought is that it would cover all the possible types of matches one might want to do on an array. Thank you for your time and help. David Alayachew -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Tue Feb 3 06:34:09 2026 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 3 Feb 2026 06:34:09 +0000 Subject: Question about Array Patterns In-Reply-To: References: Message-ID: <4A3EFD39-8A65-4B80-BD9C-11C7CE5A00E7@oracle.com> I invite you to think about how such a thing might be implemented, and what arrays are for, and the question sort of answers itself. The built-in patterns are about unpacking: recovering type information, unpacking records into their components, unpacking arrays into their contents. Asking whether an array contains exactly these elements, or even asking that question about a prefix of the elements, is an unpacking. Searching an array, as would be required by a wild card in the beginning or middle, is valid things to want to do, but it?s not unpacking. (And the cost model would be deceptive.) Arrays are a low level, built-in feature of the language, not a database to be queried. Sent from my iPad On Feb 3, 2026, at 1:01?AM, David Alayachew wrote: ? Hello @amber-dev, I was a little surprised that I didn't notice this until now, but when re-reading Brian's Array Patterns write-up, I realized that my brain auto-completed something that wasn't actually there. In the write-up, there is only the concept of a prefix match or an exact match. But what about a suffix match? Or a "somewhere-in-the-center" match? The original proposal for Array Patterns (that preceded this one) had the "..." syntax, which I mentally auto-completed to be "0 or more elements". Maybe that was a mistake on my part too. Anyways, could I get some clarification? Is that intentionally unspecified for now, pending further thought? Or is that something that will be handled else where? And by all means, what is there, I like. It's just that the message calls itself Array Patterns, so my thought is that it would cover all the possible types of matches one might want to do on an array. Thank you for your time and help. David Alayachew -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Tue Feb 3 07:22:47 2026 From: davidalayachew at gmail.com (David Alayachew) Date: Tue, 3 Feb 2026 02:22:47 -0500 Subject: Question about Array Patterns In-Reply-To: <4A3EFD39-8A65-4B80-BD9C-11C7CE5A00E7@oracle.com> References: <4A3EFD39-8A65-4B80-BD9C-11C7CE5A00E7@oracle.com> Message-ID: Thanks for the response. Ok, so there's this term unpacking. And when it's used in reference in patterns, it seems to mean the "export of contents of XYZ". Ok, cool. That makes sense then why a full array load would be an unpacking, while a suffix match is not. One must first unpack to search the tail, so that makes sense. Probably better done as a library anyways, as a user-defined pattern. I guess I just don't see how a prefix match would be considered an unpacking and not a query. What makes a prefix match an unpacking while a suffix match is a query? I'm not seeing that detail yet. But I do see why a center match is a query. That is unquestionably so. You mentioned cost model, which is the only thing I can think of that might differentiate it. I don't know the performance characteristics of getting from the front of the array vs the back, so maybe that's the gap in my knowledge that's preventing me from thinking this through? I wouldn't know where to get that info, other than attempt (and likely fail) a benchmark in JMH. But instinct (and informal tests) tells me they are equally as fast -- prefix vs suffix. On Tue, Feb 3, 2026 at 1:34?AM Brian Goetz wrote: > I invite you to think about how such a thing might be implemented, and > what arrays are for, and the question sort of answers itself. > > The built-in patterns are about unpacking: recovering type information, > unpacking records into their components, unpacking arrays into their > contents. Asking whether an array contains exactly these elements, or even > asking that question about a prefix of the elements, is an unpacking. > Searching an array, as would be required by a wild card in the beginning or > middle, is valid things to want to do, but it?s not unpacking. (And the > cost model would be deceptive.) > > Arrays are a low level, built-in feature of the language, not a database > to be queried. > > Sent from my iPad > > On Feb 3, 2026, at 1:01?AM, David Alayachew > wrote: > > ? > Hello @amber-dev , > > I was a little surprised that I didn't notice this until now, but when > re-reading Brian's Array Patterns write-up > , > I realized that my brain auto-completed something that wasn't actually > there. > > In the write-up, there is only the concept of a prefix match or an exact > match. But what about a suffix match? Or a "somewhere-in-the-center" match? > > The original proposal for Array Patterns (that preceded this one) had the > "..." syntax, which I mentally auto-completed to be "0 or more elements". > Maybe that was a mistake on my part too. > > Anyways, could I get some clarification? Is that intentionally unspecified > for now, pending further thought? Or is that something that will be handled > else where? > > And by all means, what is there, I like. It's just that the message calls > itself Array Patterns, so my thought is that it would cover all the > possible types of matches one might want to do on an array. > > Thank you for your time and help. > David Alayachew > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From florent.guillaume at gmail.com Wed Feb 4 22:24:52 2026 From: florent.guillaume at gmail.com (Florent Guillaume) Date: Wed, 4 Feb 2026 23:24:52 +0100 Subject: Incident Report 9079511: Java Language Enhancement: Disallow access to static members via object references In-Reply-To: References: Message-ID: I'd like to add (too late) that this (and all the answers by this "amazingcodewithus at gmail.com" which has no prior existence on the internet) has all the hallmarks of an LLM and imho nobody should spend time on it unless proved otherwise. The discussion was somewhat interesting though :) Florent On Fri, Jan 23, 2026 at 1:42?PM Amazing Code wrote: > > I am writing to propose a language enhancement regarding the handling of static member access in Java. > > Issue > > Java currently permits static fields and methods to be accessed through object references, despite static members belonging strictly to the class. This behavior is often misleading and can create confusion, especially in large codebases or among less-experienced developers. > > Example: > > MyClass obj = new MyClass(); > obj.staticMethod(); // Currently allowed, but confusing > > Proposed Enhancement > > I request consideration of a change that disallows access to static members via object references, enforcing access exclusively through the class name. This would convert the current warning into a compile-time error. > > Rationale > > Prevents misconceptions about instance vs. class-level behavior > > Improves code clarity and consistency > > Reduces maintenance complexity in enterprise applications > > Encourages best practices already recommended by the community > > Suggested Requirements > > Compiler should produce an error when static members are accessed through object references. > > Error messages should explicitly guide developers to use class-based access. > > Rules should apply to static fields, static methods, and static nested types. > > Optionally, provide a compiler flag for backward compatibility during migration. > > Conclusion > > Restricting object-based access to static members would strengthen language clarity and help eliminate a common source of misunderstanding. I kindly request your consideration of this enhancement for future Java releases. > > Thank you for your time and continued work on the Java platform. > > Sincerely, > Kamlesh Kohli From contact at mechite.com Thu Feb 5 09:57:39 2026 From: contact at mechite.com (Mahied Maruf) Date: Thu, 05 Feb 2026 09:57:39 +0000 Subject: Incident Report 9079511: Java Language Enhancement: Disallow access to static members via object references In-Reply-To: References: Message-ID: > all the hallmarks of an LLM I got the same IDEA but the actual discussion was something I was already curious about so no harm would have been done. From cay.horstmann at gmail.com Fri Feb 6 16:00:36 2026 From: cay.horstmann at gmail.com (Cay Horstmann) Date: Fri, 6 Feb 2026 17:00:36 +0100 Subject: Question about type pattern inside record pattern Message-ID: Consider this program: record Amount(Number n) {} Integer value(Amount p) { return switch (p) { case Amount(Integer value) -> value; case Amount(Number _) -> -1; case Amount(Object _) -> -2; }; } void main() { IO.println(value(new Amount(null))); } It prints -1. I have two questions: 1. Why does it compile? The case Amount(Object _) does not seem to reachable. 2. Why does null match case Amount(Number _) and not one of the other? I tried applying JLS (for Java 25) ?14.30, ?15.28, and ?14.11 but could not figure it out. Where should I look? Thanks, Cay -- Cay S. Horstmann | https://horstmann.com From redio.development at gmail.com Fri Feb 6 16:23:14 2026 From: redio.development at gmail.com (Red IO) Date: Fri, 6 Feb 2026 17:23:14 +0100 Subject: Question about type pattern inside record pattern In-Reply-To: References: Message-ID: This definitely looks wrong. Shouldn't a switch with a pattern matching against null either have a case or throw an exception? This behavior seems to differs between a top level match and nested record pattern match. This doesn't seem intuitive nor intentional to me. Even if we say that a _ match arm also includes null which to some extent makes sense because even if it is null it at least has to be of type Number. I would expect a Double _ arm above to not be taken as the information rather it's any subtype of Number is not there. But last but not least the Object _ shouldn't compile because it is in fact unreachable. Overall a rather quirky edge case of record pattern matching. Again I would expect an exception to be thrown because that's the behavior defined in the switch expression. Great regards RedIODev On Fri, Feb 6, 2026, 17:00 Cay Horstmann wrote: > Consider this program: > > record Amount(Number n) {} > > Integer value(Amount p) { > return switch (p) { > case Amount(Integer value) -> value; > case Amount(Number _) -> -1; > case Amount(Object _) -> -2; > }; > } > > void main() { > IO.println(value(new Amount(null))); > } > > It prints -1. > > I have two questions: > > 1. Why does it compile? The case Amount(Object _) does not seem to > reachable. > 2. Why does null match case Amount(Number _) and not one of the other? > > I tried applying JLS (for Java 25) ?14.30, ?15.28, and ?14.11 but could > not figure it out. Where should I look? > > Thanks, > > Cay > > -- > > Cay S. Horstmann | https://horstmann.com > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Fri Feb 6 16:51:48 2026 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 6 Feb 2026 17:51:48 +0100 (CET) Subject: Question about type pattern inside record pattern In-Reply-To: References: Message-ID: <1743185701.35383168.1770396708492.JavaMail.zimbra@univ-eiffel.fr> ----- Original Message ----- > From: "cay horstmann" > To: "amber-dev" > Sent: Friday, February 6, 2026 5:00:36 PM > Subject: Question about type pattern inside record pattern > Consider this program: > > record Amount(Number n) {} > > Integer value(Amount p) { > return switch (p) { > case Amount(Integer value) -> value; > case Amount(Number _) -> -1; > case Amount(Object _) -> -2; > }; > } > > void main() { > IO.println(value(new Amount(null))); > } > > It prints -1. > > I have two questions: > > 1. Why does it compile? The case Amount(Object _) does not seem to reachable. I don't know :) > 2. Why does null match case Amount(Number _) and not one of the other? I can help for this one. The long story short is that matching at top-level and de-structuring behave differently. Basically, we want case Amount(var n) or case Amount(Number n) to be equivalent semantically to case Amount _. So it means that case Amount(Number n) has to match even if n is null. The other reason is that if it does not behave that way, to be exhaustive, you will have to add case Amount(null), which does not scale once you start to de-structure several components because you have to add all the combinations. In the future, if you want to not match null, you will be able to write case Amount(Number! n) or if you want to only match null, case Amount(null). > > I tried applying JLS (for Java 25) ?14.30, ?15.28, and ?14.11 but could not > figure it out. Where should I look? > > Thanks, > > Cay regards, R?mi > > -- > > Cay S. Horstmann | https://horstmann.com From matias.koivikko at gmail.com Fri Feb 6 21:21:06 2026 From: matias.koivikko at gmail.com (Matias Koivikko) Date: Fri, 6 Feb 2026 23:21:06 +0200 Subject: Question about type pattern inside record pattern In-Reply-To: References: Message-ID: The null handling observed here does seem to be according to JLS. Specifically in ?14.30.1 one line reads: > A type pattern is said to be *null-matching* if it is appears directly in > the component pattern list of a record pattern with type R, where the > corresponding record component of R has type U, and the type pattern is > unconditional for the type U It's a bit unintuitive, but presumably this exception is made in order to allow deconstructing nulls out of records. As for the other issue, it seems like the JLS doesn't really care about reachability, but rather whether switch rules dominate each other. The Amount(Number _) rule does not dominate Amount(Object _), even though they end up with the exact same bytecode because the component type is not accounted for. Hope this helps! - Matias On Fri, 6 Feb 2026 at 18:23, Red IO wrote: > This definitely looks wrong. Shouldn't a switch with a pattern matching > against null either have a case or throw an exception? > This behavior seems to differs between a top level match and nested record > pattern match. This doesn't seem intuitive nor intentional to me. Even if > we say that a _ match arm also includes null which to some extent makes > sense because even if it is null it at least has to be of type Number. I > would expect a Double _ arm above to not be taken as the information rather > it's any subtype of Number is not there. > But last but not least the Object _ shouldn't compile because it is in > fact unreachable. > > Overall a rather quirky edge case of record pattern matching. > Again I would expect an exception to be thrown because that's the behavior > defined in the switch expression. > > Great regards > RedIODev > > On Fri, Feb 6, 2026, 17:00 Cay Horstmann wrote: > >> Consider this program: >> >> record Amount(Number n) {} >> >> Integer value(Amount p) { >> return switch (p) { >> case Amount(Integer value) -> value; >> case Amount(Number _) -> -1; >> case Amount(Object _) -> -2; >> }; >> } >> >> void main() { >> IO.println(value(new Amount(null))); >> } >> >> It prints -1. >> >> I have two questions: >> >> 1. Why does it compile? The case Amount(Object _) does not seem to >> reachable. >> 2. Why does null match case Amount(Number _) and not one of the other? >> >> I tried applying JLS (for Java 25) ?14.30, ?15.28, and ?14.11 but could >> not figure it out. Where should I look? >> >> Thanks, >> >> Cay >> >> -- >> >> Cay S. Horstmann | https://horstmann.com >> >> -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Sun Feb 8 17:47:19 2026 From: brian.goetz at oracle.com (Brian Goetz) Date: Sun, 8 Feb 2026 17:47:19 +0000 Subject: Question about type pattern inside record pattern In-Reply-To: References: Message-ID: <0A81BF57-A709-4F84-A757-6B5EE0861C6B@oracle.com> There are two things here: dominance and null handling, Let?s take them separately. Question 1 is about dominance. This is indeed confusing and I had to remind myself of why this compiles. The reason is that our dominance computation does not currently take into account the type of the selector, and so is excessively conservative in computing when cases are dead. For example: switch (anObject) { case Number n -> ? case Integer n -> ?. // compile error, dominated case } disallows the last case because `Number n` dominates `Integer n`, but this is solely based on the two types involved, not the selector. Similarly, `Box(Number)` would dominate `Box(Integer)` for the same reason. But we don?t see that `Amount(Number)` covers all cases that would be covered by `Amount(Object)` because we don?t currently take into account the type of the selector. This is a known issue and something that we?re working through. (Exhaustiveness is hard; no language that I?ve seen has gotten it right on the first try, so I?m not too surprised.) Question 2 is slightly misstated, but I think I know what you mean: you are asking why `case Amount(Number)` matches Amount(null), not matches null itself, right? The treatment of nullity in patterns is tricky. There are two intuitions that might help; some people are more compelled by one than the others, so I?ll give them both here. (Note to all: this is an explanation, not an invitation to reopen the topic, which was extensively debated, multiple times.) Explanation #1: Patterns match null, but contexts get first crack at the candidate. In this explanation, we see pattern-using constructs like `instanceof`, `switch`, etc, are in control of the operational semantics, and may or may not delegate to the pattern if it sees fit. A type pattern `Foo f` matches null, but the pattern may not always be consulted. Construct-specific rules include: - instanceof always says `false` on null, doesn?t bother to consult the pattern - switch always throws NPE on null, unless there is a `case null`, in which case that case is selected - nested pattern contexts evaluate the pattern when the subpattern itself is exhaustive, but fail to match on null when the sub pattern is not exhaustive Explanation #2: It?s about nullity inference. In this explanation, we have non-denotable patterns `Foo! f` and `Foo? f` which have the obvious semantics, and for a type pattern `Foo f` we _infer_ which of these it really means, based on context: - For top level in switch and instanceof, we infer `Foo! f` - For nested context when the sub pattern is not exhaustive, we infer `Foo! f` - For nested context when the sub pattern is exhaustive, we infer `Foo? f` The two come out the same, but some people prefer one explanation to the other. But the motivating example why we picked these rules is that they actually make sense. Consider: record Box(Object o) { } switch (box) { case Box(Chocolate c): case Box(Frog f): case Box(Object o): ?. } Which, if any, should Box(null) match? There are three options: - It matches the first one, because null matches all type patterns - It matches none of them, because null matches no type patterns - It matches the last one, because that is actually the only sensible thing to do :) The first is worse than useless; the user who wrote `case Box(Chocolate)` is asking ?does this box contain chocolate?, and will almost certainly be astonished when they find the box contains null. It will forever be a source of bugs, and even though Chocolate and Frog seem disjoint, reordering these two cases would not be safe. This is a lifetime subscription to bugs, so we can?t do this. The second is even worse, leading to an unusable language. As much as some people hate nulls and seek at every opportunity to exclude them, `new Box(null)` is an _entirely valid instance of Box_. Excluding this ?because we hate nulls? means that in order to cover the case of ?any Box, including one that contains null? requires treating `case Box(Object o)` and `case Box(null)` separately. If Box has three components, this is eight cases! The resulting feature is basically unusable in a world where nulls are real. (I realize some people would like to live in the other world, but we don?t.). The third is the only realistic option, and in reality makes a lot of sense after you think about it for a bit, but is indeed surprising at first. When we say `case Box(Chocolate)`, we?re saying ?only the boxes that contain chocolate?; that?s an intrinsically partial query. But when we say `Box(Object x)` or `Box(var x)`, we are saying, in effect, ?box containing any valid data?, and _null is valid data_. Some people had a really hard time with this, though, and bargained endlessly with this reality. Among the terrible ideas proffered were treating `case Box(Object x)` and `case Box(var x)` differently (which of course violated the principle that `var` should mean exclusively type inference, not something else.) But this is all bargaining with the essential complexity of a language that has null, which Java does. (Note that if Java ever has explicit nullity, then all this goes away because you can say `Bar! b` or `Bar? b`, and there is no ambiguity. In the meantime, we infer the thing that makes most sense given the context, which varies by syntactic location and exhaustiveness.) All of which is to say: the reality is complicated, and the ?easy? answers do not very well match the reality we live in. And also: some people hate null so much they will embrace terrible ideas to try to keep it in its ?box?. > On Feb 6, 2026, at 5:00 PM, Cay Horstmann wrote: > > Consider this program: > > record Amount(Number n) {} > > Integer value(Amount p) { > return switch (p) { > case Amount(Integer value) -> value; > case Amount(Number _) -> -1; > case Amount(Object _) -> -2; > }; > } > > void main() { > IO.println(value(new Amount(null))); > } > > It prints -1. > > I have two questions: > > 1. Why does it compile? The case Amount(Object _) does not seem to reachable. > 2. Why does null match case Amount(Number _) and not one of the other? > > I tried applying JLS (for Java 25) ?14.30, ?15.28, and ?14.11 but could not figure it out. Where should I look? > > Thanks, > > Cay > > -- > > Cay S. Horstmann | https://horstmann.com > From protonull at protonmail.com Tue Feb 10 07:32:18 2026 From: protonull at protonmail.com (Protonull) Date: Tue, 10 Feb 2026 07:32:18 +0000 Subject: "satisfies" behaviour within switch cases (JEP 530) Message-ID: Given that JEP 530 is a fourth preview, this feedback may be too late: I do not think switch patterns should do implicit type conversion without a special keyword. As while I think being able to test whether a float represents a valid int is very useful, I do not think instanceof should be misused to represent this concept of lossless implicit conversion. I instead recommend the addition of a "satisfies" keyword, like so: > if (0.5f satisfies int example) // false > if (1f satisfies int example) // true This keyword would be used in lieu of "instanceof" and "case", like so: > int result = switch (4f) { > satisfies int example -> example; > case float value -> (int) value; > }; You could then open this "satisfies" behaviour to users, allowing them to define witness-like handlers. It would then be possible to test whether a string satisfies an int, which would make configuration parsing as simple as, for example: > int port = switch(config.getString("port")) { > satisfies int valid when valid >=0 && valid <= 65535 -> valid; > case String invalid -> // print to log and yield a default port > case null -> // yield default > }; I believe this to be a clearer way to introduce the behaviour without polluting the meaning of instanceof. -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Wed Feb 11 15:23:30 2026 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 11 Feb 2026 10:23:30 -0500 Subject: Question about Array Patterns In-Reply-To: References: <4A3EFD39-8A65-4B80-BD9C-11C7CE5A00E7@oracle.com> Message-ID: <92320b52-3e1c-4305-a34e-283ac3d44de1@oracle.com> > I guess I just don't see how a prefix match would be considered an > unpacking and not a query. What makes a prefix match an unpacking > while a suffix match is a query? I'm not seeing that detail yet. But I > do see why a center match is a query. That is unquestionably so. Think about it it comparison to ? ? ?p instanceof Point(var x, _) You are logically unpacking x and y, but you don't care about y. Similarly, with a "there could be more but I don't care" on an array, you are unpacking the first N, and don't care about the rest. There are alternate ways to structure it (matching on the length instead of the body, which would admit a constant pattern) but that's a detail. In any case, we are well afield here; this particular feature is "on the shelf" for now. -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Wed Feb 11 17:06:21 2026 From: davidalayachew at gmail.com (David Alayachew) Date: Wed, 11 Feb 2026 12:06:21 -0500 Subject: Question about Array Patterns In-Reply-To: <92320b52-3e1c-4305-a34e-283ac3d44de1@oracle.com> References: <4A3EFD39-8A65-4B80-BD9C-11C7CE5A00E7@oracle.com> <92320b52-3e1c-4305-a34e-283ac3d44de1@oracle.com> Message-ID: Understood, and agreed that this discussion should be shelved for now. I'll save any follow up questions for if/when a jep or doc for this comes out. Ty vm for the context. Excited for the future of pattern matching! On Wed, Feb 11, 2026, 10:23?AM Brian Goetz wrote: > > I guess I just don't see how a prefix match would be considered an > unpacking and not a query. What makes a prefix match an unpacking while a > suffix match is a query? I'm not seeing that detail yet. But I do see why a > center match is a query. That is unquestionably so. > > > Think about it it comparison to > > p instanceof Point(var x, _) > > You are logically unpacking x and y, but you don't care about y. > Similarly, with a "there could be more but I don't care" on an array, you > are unpacking the first N, and don't care about the rest. > > There are alternate ways to structure it (matching on the length instead > of the body, which would admit a constant pattern) but that's a detail. > > In any case, we are well afield here; this particular feature is "on the > shelf" for now. > -------------- next part -------------- An HTML attachment was scrubbed... URL: From oyvind at kvien.no Fri Feb 13 13:11:01 2026 From: oyvind at kvien.no (=?UTF-8?Q?=C3=98yvind_Kvien?=) Date: Fri, 13 Feb 2026 14:11:01 +0100 Subject: Exploratory proposal: First-class namespace construct for data-oriented / functional Java Message-ID: Dear Amber team, I would like to explore the idea of introducing a first-class namespace construct in Java, intended to support data-oriented and functional programming styles without repurposing classes or interfaces as containers for static members. Motivation In data-oriented and mostly functional Java codebases, it is common to group: - Stateless utility functions - Related constants - Small domain records - Parsing and validation logic Today, the idiomatic way to express this grouping is: public final class Patient { private Patient() {} public static final String PATIENT_REFERENCE_PREFIX = "Patient/"; public static final Pattern PATIENT_IDENTIFIER_PATTERN = Pattern.compile("^Patient/(.*)", Pattern.CASE_INSENSITIVE); public record PatientIdentifier(String id) {} public static PatientIdentifier getPatientIdentifier(@Nullable Reference patientRef) { if (patientRef == null || patientRef.getReference() == null) return new PatientIdentifier(Str.EMPTY); var matcher = PATIENT_IDENTIFIER_PATTERN.matcher(patientRef.getReference()); return new PatientIdentifier(matcher.find() ? matcher.group(1) : Str.EMPTY); } } This works, but it introduces conceptual and syntactic friction: - The type is not meant to be instantiated. - The constructor must be manually suppressed. - Everything must be explicitly marked static. - The type is not modeling an object or abstraction; it is modeling a namespace. In practice, such classes act as packages within packages. An alternative is to use an interface as a namespace: public interface Patient { String PATIENT_REFERENCE_PREFIX = "Patient/"; record PatientIdentifier(String id) {} static PatientIdentifier getPatientIdentifier(...) { ... } } However, interfaces are primarily intended to model polymorphic contracts and substitution. Repurposing them as namespace containers can blur intent and introduce conceptual confusion. The well-known ?constant interface? anti-pattern illustrates this discomfort. In short, Java lacks a first-class way to express ?this is a namespace for related functions and data types.? Design Goal Provide a language-level construct that: - Represents a pure namespace (not a type with identity). - Cannot be instantiated or extended. - Groups related functions, constants, and nested types. - Supports access modifiers. - Avoids boilerplate such as private constructors and repeated static. - Preserves Java?s explicitness and readability. Strawman Syntax One possible direction: public namespace Patient { String PATIENT_REFERENCE_PREFIX = "Patient/"; Pattern PATIENT_IDENTIFIER_PATTERN = Pattern.compile("^Patient/(.*)", Pattern.CASE_INSENSITIVE); record PatientIdentifier(String id) {} PatientIdentifier getPatientIdentifier(@Nullable Reference patientRef) { ... } } Semantics (strawman) - namespace introduces a named scope at top level. - Members are implicitly static. - Fields are implicitly static final unless specified otherwise. - Nested types are implicitly static. - The namespace itself: - Cannot be instantiated. - Cannot implement or extend anything. - Cannot declare instance state. - Initialization semantics follow class static initialization rules. - At the bytecode level, the namespace may be compiled to a synthetic final class, preserving JVM compatibility. Why Not Just Use Classes? Using final classes with private constructors is serviceable but semantically misleading: - A class suggests instantiability, inheritance relationships, or type abstraction. - Namespace-only classes are often flagged as utility classes. - The pattern is common enough that it arguably deserves first-class support. Why Not Just Use Interfaces? Interfaces are designed primarily for polymorphic abstraction. Using them as namespace containers: - Conflates two distinct concepts (contract vs grouping). - Introduces ambiguity in API design intent. - Encourages patterns that may confuse less experienced developers. Providing a dedicated construct allows interfaces to remain focused on substitution and abstraction. Interaction With Existing Features Questions for exploration include: - Should namespace members require explicit static, or be implicitly static? - Should access modifiers default to the namespace?s modifier? - How do annotations apply to the namespace? - Should nested namespaces be allowed? - How does reflection expose namespaces? - How should Javadoc render them? A minimal version could require explicit modifiers and treat namespaces as a restricted form of top-level type compiled to a synthetic final class. Summary As Java evolves toward stronger support for data-oriented programming (records, pattern matching, etc.), it may be worth revisiting how we express stateless domain logic and function groupings. A first-class namespace construct could: - Reduce boilerplate. - Clarify intent. - Preserve the role of classes and interfaces. - Improve expressiveness for functional-style Java. I would be interested in feedback on: 1. Whether this problem is considered significant enough. 2. Whether a namespace construct fits Java?s philosophy. 3. Whether there are smaller or more incremental ways to address the issue. Best regards, ?yvind Kvien -------------- next part -------------- An HTML attachment was scrubbed... URL: From contact at mechite.com Fri Feb 13 19:12:21 2026 From: contact at mechite.com (Mahied Maruf) Date: Fri, 13 Feb 2026 19:12:21 +0000 Subject: Exploratory proposal: First-class namespace construct for data-oriented / functional Java In-Reply-To: References: Message-ID: Just to say that this has definitely been thought of, debated, etc. Another good reference is Lombok's `@UtilityClass`. > https://projectlombok.org/features/experimental/UtilityClass > Experimental because: > Some debate as to whether it's common enough to count as boilerplate. I consider it to be pretty bad especially when you start to introduce state with (implicitly static) fields in the namespace, where it can introduce a lot of confusion / mental overhead compared to just being happy with the `static` modifier. I feel like it could be acceptable if it was entirely stateless, to discourage (well, prevent) people from using this, then needing to make their system stateful, and abusing that to maintain ABI compatibility. Just seems like it doesn't introduce anything useful except syntactical variances and potentially adds pitfalls. At least I'd be much more interested in top-level package-private implicitly-imported stateless/static methods, if they have or ever could be considered, because the "compact source file" feature already fits with that syntactically. It would be great sugar for "utility classes" that tend to not be public API, and not remove the "discoverability" of most public API which we get from the explicit "namespacing" that static methods in classes,interfaces,etc offers. Would be great to hear thoughts. Mahied Maruf From ccherlin at gmail.com Sun Feb 22 18:21:23 2026 From: ccherlin at gmail.com (Clement Cherlin) Date: Sun, 22 Feb 2026 12:21:23 -0600 Subject: Enhanced Enums Redux (with working code) Message-ID: Greetings, I have been working on the problem of generic enums off and on for quite some time (see https://github.com/Mooninaut/cursed-generic-enum-java for a previous dead-end attempt). I think I have an actual solution this time, and it doesn't require any changes to Java's type system. It only requires a slight tweak to the "extends" clause of generic enums: Replace "extends Enum>" with "extends Enum SomeEnum>". I have a fully-worked example of this at https://github.com/Mooninaut/java-generic-enums implementing the example given in https://mail.openjdk.org/pipermail/amber-spec-experts/2017-May/000041.html, which was the stopping point for the original generic enums effort. I included lightly modified copies of Enum, EnumSet and EnumSet's subclasses, and a Main class which demonstrates that the original problems with collections of enums are almost entirely resolved. I also provide several generic enums with non-trivial type bounds to demonstrate that generic enums are no more limited than any other generic class. I cannot say definitively that every conceivable generic type will work, but I haven't yet found any generic enum that wouldn't compile and run successfully. There is one slightly awkward issue. It's not possible to simply pass an enum class literal to EnumSet's static factory methods without a raw cast. EnumSet.allOf((Class>) (Class) Option.class); But this can be worked around by taking advantage of the fact that getDeclaringClass casts to Class EnumSet.allOf(Option.D.getDeclaringClass()); Cheers, Clement Cherlin -------------- next part -------------- An HTML attachment was scrubbed... URL: From chen.l.liang at oracle.com Sun Feb 22 20:26:35 2026 From: chen.l.liang at oracle.com (Chen Liang) Date: Sun, 22 Feb 2026 20:26:35 +0000 Subject: Enhanced Enums Redux (with working code) In-Reply-To: References: Message-ID: Hi Clement, I think the issue you've discovered is already considered in the original proposal, in the "Too raw?" section: https://cr.openjdk.org/~mcimadamore/amber/enhanced-enums.html#too-raw TLDR; a reifiable type doesn't work with class literals. Regards ________________________________ From: amber-dev on behalf of Clement Cherlin Sent: Sunday, February 22, 2026 12:21 PM To: amber-dev at openjdk.org Subject: Enhanced Enums Redux (with working code) Greetings, I have been working on the problem of generic enums off and on for quite some time (see https://github.com/Mooninaut/cursed-generic-enum-java for a previous dead-end attempt). I think I have an actual solution this time, and it doesn't require any changes to Java's type system. It only requires a slight tweak to the "extends" clause of generic enums: Replace "extends Enum>" with "extends Enum SomeEnum>". I have a fully-worked example of this at https://github.com/Mooninaut/java-generic-enums implementing the example given in https://mail.openjdk.org/pipermail/amber-spec-experts/2017-May/000041.html, which was the stopping point for the original generic enums effort. I included lightly modified copies of Enum, EnumSet and EnumSet's subclasses, and a Main class which demonstrates that the original problems with collections of enums are almost entirely resolved. I also provide several generic enums with non-trivial type bounds to demonstrate that generic enums are no more limited than any other generic class. I cannot say definitively that every conceivable generic type will work, but I haven't yet found any generic enum that wouldn't compile and run successfully. There is one slightly awkward issue. It's not possible to simply pass an enum class literal to EnumSet's static factory methods without a raw cast. EnumSet.allOf((Class>) (Class) Option.class); But this can be worked around by taking advantage of the fact that getDeclaringClass casts to Class EnumSet.allOf(Option.D.getDeclaringClass()); Cheers, Clement Cherlin -------------- next part -------------- An HTML attachment was scrubbed... URL: From mfejzer at mat.umk.pl Sun Feb 22 20:45:05 2026 From: mfejzer at mat.umk.pl (mfejzer) Date: Sun, 22 Feb 2026 21:45:05 +0100 Subject: Question about introduction of Either/Result like type to the Java language Message-ID: <2268a818da2350329e845ab26e502667@mat.umk.pl> Hello, I?d like to ask what you think about the introduction of an Either/Result-like type like type to the Java language? Some attempts to write functional-like code in Java encounter no standard implementation of Either/Result-like type. Such type is usually used for explicit failure handling without exception usage, and is present in some other languages. Lack of such type in Java causes creation of many ad-hoc implementations which behave slightly differently. Examples: * https://github.com/vavr-io/vavr/blob/main/vavr/src/main/java/io/vavr/control/Either.java * https://github.com/functionaljava/functionaljava/blob/series/5.x/core/src/main/java/fj/data/Either.java * https://github.com/aol/cyclops/blob/master/cyclops/src/main/java/cyclops/control/Either.java * https://github.com/resilience4j/resilience4j/blob/master/resilience4j-core/src/main/java/io/github/resilience4j/core/functions/Either.java * https://github.com/kusoroadeolu/ferrous/blob/main/src/main/java/io/github/kusoroadeolu/ferrous/result/Result.java Introducing this type to the Java language would provide a standard implementation, allowing its use without depending on third-party libraries. Sketch, based on example from ferrous library: - java.util.Result, sealed interface permitting Success and Failure records, - records: Success(S successValue), Failure(F failureValue), - available methods: success/failure factories; map/flatMap/mapFailure/flatMapFailure; fold; peek/peekFailure; consume/ConsumeFailure etc. Non-goals: - Replacing exceptions - Extending language syntax - Introducing additional functional programming concepts (Monads, Optics etc) Kind regards, Miko?aj Fejzer From forax at univ-mlv.fr Sun Feb 22 22:03:51 2026 From: forax at univ-mlv.fr (Remi Forax) Date: Sun, 22 Feb 2026 23:03:51 +0100 (CET) Subject: [SPAM] Question about introduction of Either/Result like type to the Java language In-Reply-To: <2268a818da2350329e845ab26e502667@mat.umk.pl> References: <2268a818da2350329e845ab26e502667@mat.umk.pl> Message-ID: <1520223345.46792320.1771797831402.JavaMail.zimbra@univ-eiffel.fr> [This is my answer, not an official answer] Hello, TLDR; Java is fundamentally about being able to compose libraries freely, and you are proposing to add a new way to do function coloring [1], so No ! --- There is a deep irony in this proposal: - you want a JDK standard Either because the current ecosystem is fractured. Multiple libraries use their own custom Either types, making them impossible to compose. - the reality is that by adding Either to the JDK, you don't solve fragmentation, you evolve it. It will create a permanent schism between the Exception-based world and the Either-based world. So more incompatible libraries. Either is also an invitation to Function Coloring. In Rust, Result (their Either) is a perfect fit. Rust is a closed-world language where function coloring is a necessary tool for tracking memory safety and variable liveness. However, Java has spent the last decade moving in the opposite direction. While other languages embraced async/await (another function coloring), Java spent years of engineering effort on virtual threads specifically to avoid coloring. Adding a manual propagation mechanism like Either in the JDK would be a step backward?reintroducing the very color that the project Loom worked to erase. regards, R?mi [1] https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ ----- Original Message ----- > From: "mfejzer" > To: "amber-dev" > Sent: Sunday, February 22, 2026 9:45:05 PM > Subject: Question about introduction of Either/Result like type to the Java language > Hello, > > I?d like to ask what you think about the introduction of an > Either/Result-like type like type to the Java language? > > Some attempts to write functional-like code in Java encounter no > standard implementation of Either/Result-like type. Such type is usually > used for explicit failure handling without exception usage, and is > present in some other languages. Lack of such type in Java causes > creation of many ad-hoc implementations which behave slightly > differently. > Examples: > * > https://github.com/vavr-io/vavr/blob/main/vavr/src/main/java/io/vavr/control/Either.java > * > https://github.com/functionaljava/functionaljava/blob/series/5.x/core/src/main/java/fj/data/Either.java > * > https://github.com/aol/cyclops/blob/master/cyclops/src/main/java/cyclops/control/Either.java > * > https://github.com/resilience4j/resilience4j/blob/master/resilience4j-core/src/main/java/io/github/resilience4j/core/functions/Either.java > * > https://github.com/kusoroadeolu/ferrous/blob/main/src/main/java/io/github/kusoroadeolu/ferrous/result/Result.java > Introducing this type to the Java language would provide a standard > implementation, allowing its use without depending on third-party > libraries. > > Sketch, based on example from ferrous library: > - java.util.Result, sealed interface permitting Success and Failure > records, > - records: Success(S successValue), Failure(F failureValue), > - available methods: success/failure factories; > map/flatMap/mapFailure/flatMapFailure; fold; peek/peekFailure; > consume/ConsumeFailure etc. > > Non-goals: > - Replacing exceptions > - Extending language syntax > - Introducing additional functional programming concepts (Monads, Optics > etc) > > Kind regards, > Miko?aj Fejzer From ccherlin at gmail.com Mon Feb 23 00:30:56 2026 From: ccherlin at gmail.com (Clement Cherlin) Date: Sun, 22 Feb 2026 18:30:56 -0600 Subject: Enhanced Enums Redux (with working code) In-Reply-To: References: Message-ID: On Sun, Feb 22, 2026, 2:26?PM Chen Liang wrote: > Hi Clement, I think the issue you've discovered is already considered in > the original proposal, in the "Too raw?" section: > https://cr.openjdk.org/~mcimadamore/amber/enhanced-enums.html#too-raw > TLDR; a reifiable type doesn't work with class literals. > > Regards > That conclusion should be revisited. Reifiable types work perfectly fine with class literals, as long as you're willing to cast class literals to unerased types, and accept the fact that unerased types only truly exist at compile time. That's nothing new; all Java generics only truly exist at compile time (ignoring reflection). The only significant flaw would be if users were required to perform the awkward erased-to-raw-to-unerased cast every time they wanted to use EnumSet/EnumMap. But they don't. The method EnumClass.CONSTANT.getDeclaringClass(), a method which already exists, has exactly the desired properties. It produces the correct statically-typed cast of EnumClass.class, suitable for use in any API that requires a properly-typed Class literal, such as EnumSet, without any modification to either existing JDK code or the Java type system. As proof, I submit https://github.com/Mooninaut/java-generic-enums/blob/main/src/main/java/org/duckdns/mooninaut/genericEnum/Argument.java The claim "While in principle we could tweak the supertype of a generic enum to be Enum> instead of Enum, doing so would quickly run out of gas when considering examples such as the stream method chain shown above" is shown to be inaccurate. The code Arrays.stream(Argument.values()) .filter(a -> Number.class.isAssignableFrom(a.getClazz())) .collect(toCollection(() -> MyEnumSet.noneOf(Argument.STRING_ARGUMENT.getDeclaringClass()))); compiles and executes with no issues. It's inelegant, but it works. If elegance were required to adopt a feature into the JDK, we certainly wouldn't have generics in their current form. Cheers, Clement Cherlin -------------- next part -------------- An HTML attachment was scrubbed... URL: From redio.development at gmail.com Mon Feb 23 01:56:12 2026 From: redio.development at gmail.com (Red IO) Date: Mon, 23 Feb 2026 02:56:12 +0100 Subject: [SPAM] Question about introduction of Either/Result like type to the Java language In-Reply-To: <1520223345.46792320.1771797831402.JavaMail.zimbra@univ-eiffel.fr> References: <2268a818da2350329e845ab26e502667@mat.umk.pl> <1520223345.46792320.1771797831402.JavaMail.zimbra@univ-eiffel.fr> Message-ID: I don't see how Either for exceptions is any more or less coloring than optional is for null. The only fundamental difference is that we currently have no easy way to convert between Either and Exceptions while we have one for null and optional. The ability to wrap the conversation in a method is locked behind catch (T t) not working because of type erasure. Otherwise converting a throwing function to an either and back would be as simple as doing the same for optional. The optional currently being even worse as the optional variable could be null defeating its purpose (a different topic). Introducing an Either/Result type and adding supportive syntax/library support for it on a level of optional could join the rift to streams and other exception hostile apis without introducing any new problems or coloring as you can just "either.orThrow()" an either and "Either.of(obj::throwing)" on the other side. (strawman functions) Sure compared to optional we are required to wrap the call and not just the result which introduces issues with the Java function type system (Either.of would either only allow suppliers or havs a lot of overloads) but otherwise I see adding something like this as an absolute win in terms of Java and the jdk. And I don't see the coloring issue you describe. The only thing in java that is actually coloring is rather a method throws a checked exception or not which is the core problem trying to get addressed here. A return type can be anything it's not changing how and where a function can be called. A throws declaration is. Also result types aren't something new in Java. Look at Java sql. It uses a result type for parsing. TL;DR I don't think this introduces coloring on the opposite it would solve a coloring issue in Java. With great regards RedIODev On Sun, Feb 22, 2026, 23:04 Remi Forax wrote: > [This is my answer, not an official answer] > > Hello, > > TLDR; > Java is fundamentally about being able to compose libraries freely, > and you are proposing to add a new way to do function coloring [1], so No ! > > --- > > There is a deep irony in this proposal: > > - you want a JDK standard Either because the current ecosystem is > fractured. > Multiple libraries use their own custom Either types, making them > impossible to compose. > > - the reality is that by adding Either to the JDK, you don't solve > fragmentation, you evolve it. > It will create a permanent schism between the Exception-based world and > the Either-based world. > So more incompatible libraries. > > Either is also an invitation to Function Coloring. > > In Rust, Result (their Either) is a perfect fit. Rust is a closed-world > language where function coloring is a necessary tool for tracking memory > safety and variable liveness. > > However, Java has spent the last decade moving in the opposite direction. > While other languages embraced async/await (another function coloring), > Java spent years of engineering effort on virtual threads specifically to > avoid coloring. > > Adding a manual propagation mechanism like Either in the JDK would be a > step backward?reintroducing the very color that the project Loom worked to > erase. > > regards, > R?mi > > [1] > https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ > > > ----- Original Message ----- > > From: "mfejzer" > > To: "amber-dev" > > Sent: Sunday, February 22, 2026 9:45:05 PM > > Subject: Question about introduction of Either/Result like type to the > Java language > > > Hello, > > > > I?d like to ask what you think about the introduction of an > > Either/Result-like type like type to the Java language? > > > > Some attempts to write functional-like code in Java encounter no > > standard implementation of Either/Result-like type. Such type is usually > > used for explicit failure handling without exception usage, and is > > present in some other languages. Lack of such type in Java causes > > creation of many ad-hoc implementations which behave slightly > > differently. > > Examples: > > * > > > https://github.com/vavr-io/vavr/blob/main/vavr/src/main/java/io/vavr/control/Either.java > > * > > > https://github.com/functionaljava/functionaljava/blob/series/5.x/core/src/main/java/fj/data/Either.java > > * > > > https://github.com/aol/cyclops/blob/master/cyclops/src/main/java/cyclops/control/Either.java > > * > > > https://github.com/resilience4j/resilience4j/blob/master/resilience4j-core/src/main/java/io/github/resilience4j/core/functions/Either.java > > * > > > https://github.com/kusoroadeolu/ferrous/blob/main/src/main/java/io/github/kusoroadeolu/ferrous/result/Result.java > > Introducing this type to the Java language would provide a standard > > implementation, allowing its use without depending on third-party > > libraries. > > > > Sketch, based on example from ferrous library: > > - java.util.Result, sealed interface permitting Success and Failure > > records, > > - records: Success(S successValue), Failure(F failureValue), > > - available methods: success/failure factories; > > map/flatMap/mapFailure/flatMapFailure; fold; peek/peekFailure; > > consume/ConsumeFailure etc. > > > > Non-goals: > > - Replacing exceptions > > - Extending language syntax > > - Introducing additional functional programming concepts (Monads, Optics > > etc) > > > > Kind regards, > > Miko?aj Fejzer > -------------- next part -------------- An HTML attachment was scrubbed... URL: From atonita at proton.me Mon Feb 23 08:50:32 2026 From: atonita at proton.me (Aaryn Tonita) Date: Mon, 23 Feb 2026 08:50:32 +0000 Subject: [SPAM] Question about introduction of Either/Result like type to the Java language In-Reply-To: <1520223345.46792320.1771797831402.JavaMail.zimbra@univ-eiffel.fr> References: <2268a818da2350329e845ab26e502667@mat.umk.pl> <1520223345.46792320.1771797831402.JavaMail.zimbra@univ-eiffel.fr> Message-ID: The coloring problem is that with async/await syntax a non async function cannot directly call (await) an async function (one must access an event loop, create a new task then block). An Either/Result is just an ordinary java type, so there is no barrier to call such a function and no coloring here, but you seem to have a more general complaint. There are already two (sort of, same monadic shape but with differing semantics) such implementations in the java standard library, Future and CompletableFuture. Yet asynchronous I/O libraries often implement their own due to deficiencies in these implementations. You get callback hell with such an API (not the coloring problem) if people do not carefully factor synchronous pure functions from the processing code which fetches data with I/O. No coloring problem stops or forces this, just ordinary design difficulties, the coloring problem forces viral contagion of the async functions or extremely careful factoring. Thinking about what would be necessary for the best factoring of either sort of I/O code leads to virtual threads (as I understand it) but doesn't help with general fallible code (due to the differing semantics of Result vs Future). To be fair, people often conflate the callback hell of a Future/Promise API with the coloring problem of async/await syntax as they are two solutions to the same problem. A marked idiomatic code distinction exists between Result returning APIs and exception handling APIs in their handling of error branching (callback vs effect system), I believe this is what you are objecting to Remi? The encouragement of code that would tend to callback hell and its marked difference with respect to "ordinary" exception handling? When implementing or refactoring the very same potential functionality one person might say, "I want the same catch block to simply handle all IOException's," and another person might say of the same, "I don't want a single catch block to implicitly handle all IOException's." Or, "I don't want to remember the distinction between map and flatMap,", vs "I want to have a distinguished flatMap so I can choose to join errors or not for a single error handling or distinguished handling." Some of the best ways to avoid callback hell will also come if/when we can pattern match on Result/Optional (not on Future). Although truly the best comes with an early return/try operator (? in rust). This stylistic bifurcation already exists and I don't think having a standard answer for Result would necessarily increase the number of people reaching for that solution. Like Miko?aj, I also dislike the need to pull in a library just to get Either/Result, and certainly often feel the need (with streams or when designing a callback api and needing to make the same call or not as the stream api with respect to exceptions on the callback). Another reason I brought up CompletableFuture is to indicate that it would probably need some level of love to actually provide a good API that people wouldn't seek to reimplement. Its semantics with respect to null would probably be contentious although maybe we are approaching the end of that with null restricted types. The lack of a standard Result definitely feels like a hole, especially with respect to streams. Encountering that hole and working around it is practically a rite of passage now! That rite of passage leads some people to desire a complete lack of checked exceptions (and use of sneaky throws), some people to reach for Result, other people to refactor to imperative iteration, and so on. Even if you aren't in that camp, it would be nice if the people in the Result camp could have a standard batteries-included implementation just so you would have a standard path back to your own camp (and hopefully a smaller dependency footprint). On Sunday, February 22nd, 2026 at 11:05 PM, Remi Forax wrote: > [This is my answer, not an official answer] > > Hello, > > TLDR; > Java is fundamentally about being able to compose libraries freely, > and you are proposing to add a new way to do function coloring [1], so No ! > > --- > > There is a deep irony in this proposal: > > - you want a JDK standard Either because the current ecosystem is fractured. > Multiple libraries use their own custom Either types, making them impossible to compose. > > - the reality is that by adding Either to the JDK, you don't solve fragmentation, you evolve it. > It will create a permanent schism between the Exception-based world and the Either-based world. > So more incompatible libraries. > > Either is also an invitation to Function Coloring. > > In Rust, Result (their Either) is a perfect fit. Rust is a closed-world language where function coloring is a necessary tool for tracking memory safety and variable liveness. > > However, Java has spent the last decade moving in the opposite direction. While other languages embraced async/await (another function coloring), Java spent years of engineering effort on virtual threads specifically to avoid coloring. > > Adding a manual propagation mechanism like Either in the JDK would be a step backward?reintroducing the very color that the project Loom worked to erase. > > regards, > R?mi > > [1] https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ > > > ----- Original Message ----- > > From: "mfejzer" > > To: "amber-dev" > > Sent: Sunday, February 22, 2026 9:45:05 PM > > Subject: Question about introduction of Either/Result like type to the Java language > > > Hello, > > > > I?d like to ask what you think about the introduction of an > > Either/Result-like type like type to the Java language? > > > > Some attempts to write functional-like code in Java encounter no > > standard implementation of Either/Result-like type. Such type is usually > > used for explicit failure handling without exception usage, and is > > present in some other languages. Lack of such type in Java causes > > creation of many ad-hoc implementations which behave slightly > > differently. > > Examples: > > * > > https://github.com/vavr-io/vavr/blob/main/vavr/src/main/java/io/vavr/control/Either.java > > * > > https://github.com/functionaljava/functionaljava/blob/series/5.x/core/src/main/java/fj/data/Either.java > > * > > https://github.com/aol/cyclops/blob/master/cyclops/src/main/java/cyclops/control/Either.java > > * > > https://github.com/resilience4j/resilience4j/blob/master/resilience4j-core/src/main/java/io/github/resilience4j/core/functions/Either.java > > * > > https://github.com/kusoroadeolu/ferrous/blob/main/src/main/java/io/github/kusoroadeolu/ferrous/result/Result.java > > Introducing this type to the Java language would provide a standard > > implementation, allowing its use without depending on third-party > > libraries. > > > > Sketch, based on example from ferrous library: > > - java.util.Result, sealed interface permitting Success and Failure > > records, > > - records: Success(S successValue), Failure(F failureValue), > > - available methods: success/failure factories; > > map/flatMap/mapFailure/flatMapFailure; fold; peek/peekFailure; > > consume/ConsumeFailure etc. > > > > Non-goals: > > - Replacing exceptions > > - Extending language syntax > > - Introducing additional functional programming concepts (Monads, Optics > > etc) > > > > Kind regards, > > Miko?aj Fejzer > From maurizio.cimadamore at oracle.com Mon Feb 23 12:26:38 2026 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Mon, 23 Feb 2026 12:26:38 +0000 Subject: Enhanced Enums Redux (with working code) In-Reply-To: References: Message-ID: Hi Clement On 22/02/2026 18:21, Clement Cherlin wrote: > Greetings, > > I have been working on the problem of generic enums off and on for > quite some time (see > https://github.com/Mooninaut/cursed-generic-enum-java for a previous > dead-end attempt). > > I think I have an actual solution this time, and it doesn't require > any changes to Java's type system. It only requires a slight tweak to > the "extends" clause of generic enums: Replace "extends > Enum>" with "extends Enum SomeEnum>". I'd need more time to think about the consequences of this. That said, this has a good property, in that it only affects generic enum declarations. Non-generic enum would stay exactly the same. > > There is one slightly awkward issue. It's not possible to simply pass > an enum class literal to EnumSet's static factory methods without a > raw cast. > > EnumSet.allOf((Class>) (Class) Option.class); This makes me more worried -- because this is such a common idiom that (I think) has to work like for any other enum. In the spirit of your earlier suggestion, a possible way out would be to also redefine what Foo.class mean when Foo is a generic enum -- e.g. not just Class (class-of-raw) but Class> (class-of-wildcard). All such changes would? indeed help making generic enums more useful (and interoperable with EnumSet and friends), but whether all such changes are actually "sound", that's a different story. For instance, code like: MyEnumSet