Discussion: The Glue Classes - Explicit, Modular Type Extension
Marek Kozieł
develop4lasu at gmail.com
Sun Feb 1 15:33:56 UTC 2026
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 <G> void glue(Iterator<G> 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 <G> void glue(G[] this){ /* empty */ }
|
| // -1 in extractByIndexes uses null
| public static <G> G[] extractByIndexes(G[] this, int... indexes) { /*
... */ }
|
| public static <G> 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 <G> 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 <G> void glue(G[] this){ /* empty */ }
|
| public static <G> 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 <G> void glue(Iterator<G> this){}
|
| public static <G, R> Iterator<R> map(Iterator<G> this, Function<?
super G, ? extends R> fn) {
| // Implementation: returns an Iterator<R> that applies fn to each
element of original Iterator<G>
| }
| }
and usage would be:
| Iterator<String> it = ...;
| Iterator<Integer> numbers = it..map(String::length);
Name collisions
Following Java namespace ruless we could be more precise and use longer
names:
| Iterator<Integer> numbers = it..IteratorGlue.map(String::length);
or if needed fully qualified name
| Iterator<Integer> 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> 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 <G> void glue(G[] this){ /* empty */ } // need to be
restated to eliminate collision
|
| public static <G> G[] collision(G[] this){
| return this..ArrayApacheGlue.collision();
| }
|
| // Marking already existing method as depeciated
| @Deprecated
| public static <G> 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 <G> void glue(G[] this){ /* empty */ }
|
| public static <G> G[] setOn(G[] this, int index, G value) { /* ... */ }
|
| public static <G> G[] copyWithSetOn(G[] this, int index, G value) { /*
... */ }
| }
could be re categorized to mutable and immutable spaces:
| public class ArrayGlue{
| public final static <G> void glue(G[] this){ /* empty */ }
|
| public static class immutable{
| public final static <G> void glue(G[] this){ /* empty */ }
|
| public static <G> G[] setOn(G[] this, int index, G value) { /* copy
is made */ }
| }
|
| public static class mutable{
| public final static <G> void glue(G[] this){ /* empty */ }
|
| public static <G> 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 <G> void glue(G[] this) {}
| public static <G> G[] map(G[] this, Function<G,G> fn) { ... }
| }
// Library Y
| public class YArrayGlue {
| public static <G> void glue(G[] this) {}
| public static <G> G[] map(G[] this, Function<G,G> 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 <K, V> Map<K, V> listToMapFrame(List<K> 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<String, Integer> m = Maps.<String, Integer>
listToMapFrame(List.of("a", "b", "c"));
we could have:
| Map<String, Integer> m = Maps.<String, ?? > 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 <G> void glue(Iterator<G> this){}
|
| public static <G, R> Iterator<R> map(Iterator<G> this, Function<?
super G, ? extends R> fn) {
| // Implementation: returns an Iterator<R> that applies fn to each
element of original Iterator<G>
| }
| }
| Iterator<String> it = ...;
| Iterator<Integer> 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<Integer> numbers =
it..package.IteratorGlue.<Integer>map(String::length);
// under glue we would have <[G,] R> so both <R> and full <G, R> could
be used
decoded to:
| Iterator<Integer> numbers = ..package.IteratorGlue.<??, Integer>map(it,
String::length);
instead of:
| Iterator<Integer> numbers =
it..package.IteratorGlue.<String,Integer>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
<Integer> on a <G, R> method is interpreted as <??, Integer>.
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 <G> void glue(G[] this){ /* empty */ }
|
| // -1 in extractByIndexes uses null
| public static <G> G[] extractByIndexes(G[] this, int... indexes) { /*
... */ }
|
| public static <G> G[] setOn(G[] this, int index, G value) { /* ... */ }
| }
into (under the hood it could be still glue-method):
| public glue class ArrayGlue<G> 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<List<String>> groups = new ArrayList<>();
| boolean[] used = new boolean[dict.length];
|
| for (int i = 0; i < dict.length; i++) {
| if (used[i]) continue;
| List<String> 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<String[]> mappedGroups = new ArrayList<>();
| for (List<String> 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<T> glue(T) {
| public glue boolean equals(this, T other) { /* ... */ }
| }
| public class StringGlue glue(String) {
| public glue Character firstChar(this) { /* ... */ }
| }
| public class ArrayGlue<T> glue(T[]) {
| public glue Iterator<T[]> iterateByConnection(this, BiPredicate<T, T>
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<T> glue(Iterator<T>) {
| public glue T[] collectToArray(this, Class<T> componentType) { /* ...
*/ }
|
| public glue <R> Iterator<R> map(this, Function<? super T, ? extends R>
fn) { /* ... */ }
| }
Glue to static tranformation:
| public class
| IteratorGlue<T> glue (Iterator<T>) {
| ↓ ↓
| public glue < R> Iterator<R> map( this, Function<? super T, ? extends
R> fn) { /* ... */ }
=
| public static <T, R> Iterator<R> map( Iterator<T> this, Function<? super
T, ? extends R> 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<T> glue(T[]) {
| // core glue methods (iterate, size, extractByIndexes, ...)
|
| public static glue class immutable<T> 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<T> 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<T> extends ArrayGlue<T> glue(T[]) {
| public static class immutable<T> extends ArrayGlue.immutable<T>
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..<TypeArg>glueMethod(...)
variable..<GlueClassTypeArg><TypeArg>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., <TX> ensureNotNull(T o)). In
principle, even signatures like <TX> 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 <T, X> 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<T>) 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<T>) 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: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20260201/be660723/attachment-0001.htm>
More information about the amber-dev
mailing list