Today, August 13, 2026, marks exactly 8 years since Brian Goetz filed JEP 8209434: Concise Method Bodies — a draft proposal to align method body syntax with lambda expression syntax in Java.
Eight years. Still in draft status. Still not in any Java release.
So I built it myself with Kiro.
What JEP 8209434 Proposes
Two new forms for method bodies that eliminate boilerplate in delegation and simple methods:
// Expression form: ->
public int size() -> c.size();
// Method reference form: =
public int size() = aList::size;
These expand to exactly what you'd expect:
public int size() { return c.size(); }
public int size() { return aList.size(); }
If you've ever written a decorator, adapter, or any class that delegates to another object, you know the pain. The JDK's own Collections.UnmodifiableCollection has 14 methods that are nothing but { return c.something(); }. With concise method bodies, they become one-liners.
Why Wait?
The JEP has been in "Draft" status since 2018. No target release. No preview flag. Meanwhile, C# shipped expression-bodied members in 2015. Kotlin has had single-expression functions since day one.
Java developers deserve this too. And there's nothing preventing us from having it today — as a source code preprocessor.
The Preprocessor
java-composition is a source-to-source transformer. You write concise method bodies in your .java files, and the preprocessor expands them into standard Java before javac ever sees them.
Your IDE sees the concise source. The compiler sees standard Java. Any Java version from 8 onwards.
The Expression Form (->)
Purely syntactic — no type resolution needed:
// Non-void: wraps in return
String getName() -> this.name;
int add(int a, int b) -> a + b;
int abs(int x) -> x >= 0 ? x : -x;
// Void: expression statement
void close() -> stream.close();
void log(String msg) -> System.out.println(msg);
The Method Reference Form (=)
The method reference is invoked with the method's parameters. The preprocessor infers which method is being referenced and generates the correct invocation:
// Bound instance — receiver is the expression before ::
public int size() = aList::size;
// → return aList.size();
// Unbound instance — first parameter becomes receiver
boolean isEmpty(String s) = String::isEmpty;
// → return s.isEmpty();
// Static — all parameters become arguments
int max(int a, int b) = Math::max;
// → return Math.max(a, b);
// Constructor
static Foo make(int a, int b) = Foo::new;
// → return new Foo(a, b);
Real-World Example: JDK's UnmodifiableCollection
Before:
static class UnmodifiableCollection<E> implements Collection<E> {
final Collection<? extends E> c;
public int size() {return c.size();}
public boolean isEmpty() {return c.isEmpty();}
public boolean contains(Object o) {return c.contains(o);}
public Object[] toArray() {return c.toArray();}
public <T> T[] toArray(T[] a) {return c.toArray(a);}
public String toString() {return c.toString();}
// ... 8 more like this
}
After:
static class UnmodifiableCollection<E> implements Collection<E> {
final Collection<? extends E> c;
public int size() -> c.size();
public boolean isEmpty() -> c.isEmpty();
public boolean contains(Object o) -> c.contains(o);
public Object[] toArray() -> c.toArray();
public <T> T[] toArray(T[] a) -> c.toArray(a);
public String toString() -> c.toString();
// ... 8 more like this
}
Same semantics. Half the noise.
How It Works
Under the hood, the tool:
-
Forks JavaParser (the standard Java parser library) and extends its grammar to recognize
-> Expression ;and= MethodReference ;as valid method bodies - Stores concise bodies in the AST without expanding them — this enables future transformations to build on top
-
Runs transformation passes that expand concise forms into standard
BlockStmtbodies - Pretty-prints the result as standard Java
For the = form, the preprocessor infers the referenced method from the source tree and classpath to generate the correct invocation. This is the only part that requires configuration (a classpath parameter).
API
// Expression form only (no classpath needed)
var preprocessor = new Preprocessor(sourceRoot, targetRoot);
preprocessor.process(Path.of("com/example/MyClass.java"));
// Method reference form (classpath needed for type resolution)
var preprocessor = new Preprocessor(sourceRoot, targetRoot, List.of(
Path.of("lib/dependency.jar")
));
preprocessor.process(Path.of("com/example/MyClass.java"));
Works on Any Java Version
The output is plain Java. No lambdas, no var, no records — just method bodies with return statements. If your project targets Java 8, the output compiles with Java 8. If you target 21, same.
The preprocessor itself runs on Java 8 and above — it has no runtime dependency on modern Java features.
What's Next
This is just the beginning. The project roadmap includes:
-
Maven plugin — drop-in build integration (
generate-sourcesphase) - Gradle plugin — same for Gradle
- Shaded JAR — single dependency with no classpath conflicts
-
Published to Maven Central —
guru.mocker:java-composition
And beyond that, the real dream: wildcard delegation.
class MyList<T> implements List<T> {
private final List<T> delegate;
List::* = delegate::*; // All List methods delegate to 'delegate'
// Override only what you need
public boolean add(T e) {
log("adding: " + e);
return delegate.add(e);
}
}
One line to generate dozens of forwarding methods. Composition as a first-class pattern.
Try It
The code is at github.com/verhasi/java-composition. Clone, build, and start writing concise method bodies today.
git clone git@github.com:verhasi/java-composition.git
cd java-composition
cd javaparser && ./mvnw clean install -DskipTests && cd ..
mvn clean test -pl preprocessor
All 31 tests pass. Real-world tested against the JDK's own Collections.java.
Happy 8th birthday, JEP 8209434. We got tired of waiting.
Top comments (0)