What it is
Computed, not declared.
maddi is a whole-program static analyzer for
Java — and for Kotlin, through a shared syntax tree. You don’t write annotations:
maddi computes them. It reads your sources and your classpath,
follows how objects flow between fields, parameters and return values, and derives
@Immutable, @Container, @Modified and
@Independent for every type, method and field.
You write
public class Range {
private final int lo;
private final int hi;
}maddi computes
@Immutable
@Container
public class Range {
private final int lo;
private final int hi;
}Nothing in your source has to change. maddi derives the annotations — and where you do write them yourself, it checks them against what it computed.
How it reports
How far each type got.
Real code is rarely deeply immutable, and a yes/no answer throws away everything useful. maddi reports how far a type got, and what stopped it going further.
Every type lands on one of four levels. Which one it reaches tells you how far you got — and the reason it stopped there is exactly what you would have to change to go further.
hc=true records the one catch: a link runs to the elements of a type parameter maddi cannot see into. It is not hc-free @Immutable, and the analyzer does not pretend otherwise.
@Mark, @Only and @Immutable(after=…) describe the builder and freeze patterns: types that are mutable while being constructed, and immutable ever after.
In your own code
Use the annotations without adopting the analyzer.
The one thing your own code compiles against is the annotations library. It is on Maven Central, targets Java 17, and is Apache-2.0 — so you can depend on it without taking on the analyzer’s LGPL.
Gradle
implementation("io.codelaser:maddi-annotation:0.9.1")Maven
<dependency> <groupId>io.codelaser</groupId> <artifactId>maddi-annotation</artifactId> <version>0.9.1</version> </dependency>
That gives you @Immutable, @Container,
@Independent, @Modified and friends, with no
dependencies at all — useful as documentation and as contracts on your
interfaces even before you run the analyzer, since maddi verifies them against
what it computes.
If you also want the eventually-final support classes — SetOnce,
Freezable, EventuallyFinal, Lazy and
FirstThen — depend on io.codelaser:maddi-support
instead. It declares exactly one dependency, on maddi-annotation, and
re-exports it. Versions up to 0.8.2 were LGPL-3.0;
0.9.0 onward is Apache-2.0.
Where it stands
Status — August 2026
0.9.1 is the first release where
every part is installable: the Gradle plugin on the Plugin Portal, the Maven plugin
and the annotations on Maven Central, and self-contained command-line distributions
on GitHub Releases.
| Part | State |
|---|---|
| Concepts, and the book | Stable |
| Parser / resolver (javac front end) | Solid; run against many open-source projects and one closed-source 3M-line codebase |
| Modification & immutability analysis | Settles on the same verified answer every time, on all our test projects — Timefold, LangChain4j, Fernflower, Guava, ActiveMQ, Jenkins, Camel |
| Gradle / Maven plugins | Published in 0.9.1; both tested against a set of real multi-module builds |
| Kotlin front end | Works; ships only via the maddi-kotlin command-line download |
The analyzer is stable on everything we run it on — and your codebase will contain Java that ours do not. If maddi mis-reads your code, crashes, or works something out you can argue is wrong, that is exactly the report we want: please open an issue.
Where it came from
From e2immu to maddi.
maddi re-implements e2immu, which ran from
2020 until it was archived. Same ideas, rewritten analyzer, now covering Java and
Kotlin. The root Java package was org.e2immu.*, after its
predecessor; it became io.codelaser.maddi.* in 0.9.1.
maddi is developed by Bart Naudts at CodeLaser, and is and will remain open
source. The analyzer is LGPL-3.0; the annotations
— the only artifacts your own code compiles against — are Apache-2.0 from
0.9.0 onward. CodeLaser’s commercial engine is built on this
analyzer; the analyzer stays here, under this licence.