Repository navigation
maddi 0.9.5
maddi now translates Java. maddi --translate kotlin prints a whole Java program as idiomatic Kotlin.
--translate csharp prints it as C#, which is experimental. A new nullability analysis decides what may be null in
the output, and the modification analysis decides which collections are read-only. Both run on the same linking
engine as the immutability analysis, which is now one application of that engine among three.
0.9.5 is the first public release since 0.9.1; 0.9.2 and 0.9.3 were internal. Code that compiles against the 0.9.1
annotations compiles against 0.9.5 unchanged. Two changes to the command line can affect existing scripts; see
Changes that can affect you.
Java → Kotlin translation
-
--translate kotlin --translate-output <dir>on themaddicommand line. By default the modification analysis
and the nullability pass run first and their facts drive the printer;--analysis-steps nonetranslates without
them. The output directory gets atranslation-report.txtthat lists printer crashes and messages. A crash leaves
that one file out and exits non-zero. -
The printer covers all of Java's syntax. What makes the output idiomatic comes from the analyses:
TorT?from the nullability verdicts;?.and?:from Java's null checks, and!!only where the analysis knows more than the types say;ListorMutableList, andvalorvar, from the modification facts;- data classes from records, and expression bodies;
- JSpecify and type-use
@Nullableannotations in the input are kept.
-
Measured on whole corpora. Six open-source corpora are printed as Kotlin and compiled with
kotlinc:corpus files compiling Fernflower 199 198 LangChain4j core 346 346 Guava 610 603 Nacos client API 274 271 Jenkins core 1,329 1,271 Timefold Solver core 1,583 1,361
Java → C# translation (experimental)
-
--translate csharp --translate-output <dir>writes C# under#nullable enableand aTranslated.csproj.
Roslyn judges the output:corpus files compiling Fernflower 199 185 LangChain4j core 346 301 Timefold Solver core 1,583 1,324 -
The JDK is mapped onto the .NET base library: collections, LINQ for streams,
java.timeand concurrency. Where
there is no counterpart, a small compatibility layer is written next to the output. -
The analyses decide
?and!,IReadOnlyList<T>for parameters that are never modified, properties for getters
and setters, delegates for functional interfaces, andsealedfor what nothing extends.
Nullability analysis
- A new pass runs after the modification analysis, on its link facts. It decides, for every field, parameter, method
return and type argument, whether it can holdnull. - The JDK's annotated API now carries nullness generated from JSpecify's annotated JDK, type arguments and array
elements included. Libraries marked@NullMarkedare honoured. - In 0.9.5 the verdicts reach users through the translations. Writing them back into Java as
@Nullable/@NonNull
is implemented in the engine but not yet exposed on the command line or in the plugins.
Kotlin
- Close to parity with Java. The analysis stack below the front end is shared, and has no Kotlin-specific paths.
Test tiers of Kotlin/Java fixture pairs check that both get the same verdicts. A long series of K2 front-end fixes
closed the shapes that converted silently wrong: local delegated properties, enum entries with bodies, class
delegation, operator overloads, named-argument evaluation order, multi-typewhenarms, and the inlined scope
functionsapply,also,let,run,withanduse. - The Kotlin stdlib's annotated API grew from one file to 48 types and 2,252 members.
maddi-kotlintakesmaddi's command line. It routes on content: a project without.ktfiles runs exactly
asmaddidoes. An option the mixed pipeline cannot honour yet is refused by name, with exit code 7.
--skip-kotlin-sourcesanalyses only the Java of a mixed project, accepting an incomplete result.
Java front end
- JDK 27. maddi parses and runs on JDK 27, including javac 27's new trees, and its JDK annotated API is generated
from JDK 27. - Stability:
- A failing annotation processor no longer drops a whole source set. A Lombok processor that cannot run is reported
as a parse error. - Unresolved calls and imports become unresolved symbols, not errors.
- A compilation unit that ends up with neither types nor a failure is reported.
- Receiver parameters parse. Deeply recursive generics no longer overflow the stack.
- A failing annotation processor no longer drops a whole source set. A Lombok processor that cannot run is reported
--compile-logalso reads Ant build logs and shaded jars.- The parser logs one INFO line per parse.
Modification and immutability analysis
- A reachability pass after the fixpoint propagates modification along call chains. Several fixes stop it from
over-modifying delegating implementations and chained receivers, or from losing a lambda's modification of its
enclosing instance. - Eventual immutability: after the mark, what a value shares decides its verdict, not its type.
- A captured parameter counts as a field for independence. A view shares the modification of the collection it views.
@UnmodifiableView, a new annotation inmaddi-annotation, on a method whose result is an object nothing is
added to or removed from through that reference, such asCollections.unmodifiableList(list). It is weaker than
@Immutable(hc=true), which a view must not claim. A contract on bodiless methods (the JDK'sCollections.unmodifiable*
carry it); computed from the returned values for a method with a body. The Kotlin translation reads it forList
versusMutableList.
Annotated APIs
-
The JDK grew from 249 types / 5,664 members to 527 / 9,493. The additions include read-only arguments found by
a census of real call sites,java.time,java.util.jar, formatting,Collections.unmodifiable*and the loggers. -
libs.jar, shipped with maddi, now covers these libraries:- AssertJ, hamcrest, JUnit Jupiter params and Mockito;
- Guava collect, commons-io and Jackson 2.
slf4j's
Loggeris now a@Containerwhose log methods ignore their own modification. In 0.9.1 it was immutable. -
Analysis results now state every verdict explicitly.
Changes that can affect you
- The shipped results load by default. The CLI and the Gradle and Maven plugins now load the JDK and library
results that come with maddi when you name no--preload-analysis-results-dirs.noneloads nothing, and
defaultnames the shipped results in a list of directories. -smeans--sourceonly. In 0.9.1 the short option-swas declared for both--sourceand
--preload-analysis-results-dirs. Use the long option for the second.- Java entry points refuse Kotlin sources. Before, they skipped them silently. Use
maddi-kotlin, or
--skip-kotlin-sources.
Installing
Nothing about the coordinates changes, apart from the version.
| What | Where |
|---|---|
| Gradle plugin | id("io.codelaser.maddi.analyzer") version "0.9.5", on the Gradle Plugin Portal |
| Maven plugin | io.codelaser:maddi-mvnplugin:0.9.5, on Maven Central |
| Annotations | io.codelaser:maddi-annotation:0.9.5 and io.codelaser:maddi-support:0.9.5, on Maven Central (Apache-2.0) |
| Command line | maddi-0.9.5.zip and maddi-kotlin-0.9.5.zip, on the GitHub release |
The analyzer needs JDK 25 or later to run, and the Kotlin distribution needs JDK 26 or later. The IDE plugins
(IntelliJ, Eclipse, VS Code) are not published; build them from source in
maddi-dist.
For contributors: three repositories
On 2026-09-30 maddi was split by tier into three repositories:
- maddi: the base, with the CST, the front ends, the printers, the annotated APIs and the corpus tooling.
- maddi-mod: the analyses.
- maddi-dist: the CLI, the plugins and the IDEs.
maddi-dist compiles against the base only, and a build check enforces it. Build from source with the three checked out
side by side (see the README). Development is on JDK 27.
Known limitations
--translaterefuses a project with Kotlin sources: there is no single Java program to translate.- C# output is judged on compilation only; no corpus's tests run against it yet.
--translatedoes not expose the printers' options yet.- The Kotlin stdlib's annotated API is still smaller than the JDK's, and an uncontracted library call counts as
modifying.
Documentation
- The README, rewritten for this release.
kotlin-printing.mdand
csharp-printing.md, for how the translations work.- The user manual and the book,
The Road to Immutability, which gains a chapter on nullability
(sources): the definitions, what the
analyzer computes and how, and the closed and open world.