Portfolio Manifest — Prepared 2026-08-23
Full-Stack Java/Gradle Engineer
A deliberate migration of Axon Framework 5's build system from Maven to Gradle — converted
one file at a time, each one individually inspected against the Maven source rather than run
through an automated converter. Build-logic conventions are done, test-logging is published, and
common's source conversion is underway (17 files in) — every gap found along the way, eight so
far, is disclosed in this repo's own build log, not smoothed over. Genuinely early — 13 of 14 reactor modules
haven't started converting at all.
A real Maven→Gradle migration, verified rather than trusted
Pointing a conversion tool at a Maven reactor and committing whatever comes out would be faster. It would
also miss everything interesting: Gradle has no equivalent of Maven's parent-POM inheritance, so a shared
dependency block that's harmless in axon-parent becomes a literal circular dependency the
moment it's mechanically applied to the one module that dependency points at. A checkstyle rule copied
byte-for-byte looks complete right up until a second read catches the comment three lines above it
explaining why one entry is deliberately missing — caught before landing, not after, but wrong on the
first pass all the same.
Build-logic conventions done · test-logging published · common 17/218 files in · 12 modules not started
Migration order is deliberate and bottom-up: the shared Gradle conventions every module will apply — Java
toolchain, checkstyle, publishing, signing — had to exist before any real module could be converted onto
them. test-logging was the first module through that pipeline, published for real
(org.axonframework:axon-test-logging) with its own genuine circular-dependency problem solved
along the way. common is next, converted file by file in dependency order (base exception
hierarchy first, then each utility class once whatever it references exists) — 17 of 218 files so far. The
rest of the reactor — conversion, messaging, eventsourcing,
modelling, and eight more — hasn't started; see
todo.md
for the honest current state rather than assuming more is done.
Every structural change gets a real gradle build or gradle :projects run
before being called done — including re-running it live when a fix's correctness was itself in
question, not just reasoning about what the fix should do.
No gradle/libs.versions.toml version catalog (dependency-drift risk accepted, not
overlooked) and integrationtests deliberately not publishing to Maven Central, diverging
from upstream's own current behavior — both written down in
todo.md, not silently absorbed.
Found by running a real build or reading a live API doc, not by assuming the first pass was right
Three turned up in this fork's own Gradle translation of Maven's inheritance and publishing model. One
turned up in a third-party plugin's live interaction with a specific Gradle version, undocumented anywhere
public at the time. One turned up in this fork's own .gitignore. One turned up in upstream
Axon's own Maven config — real drift in a widely-used project, not a typo. One turned up in Axon's own HTML
hitting a stricter tool than upstream builds with. One turned up in the CI workflow's own first real run.
axon-parent unconditionally adds axon-test-logging as a test
dependency to every inheriting module — upstream's own pom.xml dodges the resulting
self-reference by parenting that one module on the root aggregator instead. Gradle's convention-plugin
model has no equivalent "skip one level of inheritance" lever, and the shared
java-conventions plugin already had that dependency line, so applying it to
test-logging itself would create a literal self-dependency. Guarded with a one-line
if (project.name != "test-logging"), verified with a real
gradle :test-logging:dependencies run showing no self-reference in the resolved graph.
nmcpSettings { centralPortal { username = providers.environmentVariable(...) } }
fails Gradle 9.2's configuration cache with cannot serialize object of type ...
ValueSourceProvider. Reproduced directly by running the build, not inferred from a stack trace
read in isolation — searched publicly first and found nothing documented about it. Fixed with a
plain System.getenv(...) read instead of the lazy provider.
build/ directory does double duty: a real, intentionally-tracked module
directory (mirroring Maven's build/parent, build/checkstyle.xml) and
Gradle's default build-output location for the root project — the same path, two purposes. A blanket
.gitignore exception meant to protect the former was quietly letting Gradle's own
generated reports through as trackable files. Replaced with an explicit allow-list of just the real
paths, verified with git check-ignore -v against both cases before trusting it.
include(...) in settings.gradle.kts pointed at a module directory
that didn't exist yet, and Gradle refuses to configure a build at all under that condition — not just
skip the missing module. Confirmed with a real failing gradle run before deciding on a
fix. Fixed by commenting each include out individually, brought back one at a time as that module is
actually converted.
axon-todo, axon-legacy, axon-legacy-aggregate, and
axon-legacy-saga appear in upstream's maven-javadoc-plugin configuration but
match nothing in this Axon 5 reactor's actual module list — uncleaned drift from an Axon 4-era POM,
confirmed by cross-checking every real module name. Not carried forward.
axon-<name>, but Gradle's
base.archivesName defaults to the bare project directory name — and, confirmed by
generating the actual POM rather than assuming the jar filename being right meant the coordinate was
too, MavenPublication.artifactId doesn't inherit from archivesName either; a
second, separate default needed overriding. test-logging had already been generating a
POM with <artifactId>test-logging</artifactId> instead of
axon-test-logging since its own commit. Fixed once, centrally, for every module.
common got its first real source file, javadoc failed with
error: self-closing element not allowed on a <p/> tag. Not a source
bug to hand-fix: upstream's own root pom.xml already disables doclint entirely
(<doclint>none</doclint>) for exactly this reason — JDK 25's stricter
javadoc, not upstream's. Gradle's side had just never replicated that suppression. Fixed centrally,
confirmed with a clean gradle :common:build afterward.
gradle-version: '9.2' in the new build-verification workflow failed immediately with
Error: Gradle version 9.2 does not exist — gradle/actions/setup-gradle needs
the exact release string, not Gradle's own shorthand. Caught by watching the actual run, not assumed
green because the YAML looked right. Fixed to '9.2.0', verified with a second real run
that took 1m53s instead of 15 seconds — the difference between failing at setup and actually building.
./gradlew. Blocked until now by gap
#04 above; tracked openly in todo.md as its own still-open item, not assumed fixed just
because the bigger blocker is gone.
todo.md, README, the wiki, the diagrams, this page, and the project board — checked, not assumed in sync
A direct review caught something a self-driven sweep hadn't: "Axon Framework 5" was hyperlinked to this
fork's own repository in three separate places — README.md, todo.md, and the
wiki's home page — which read as a project linking to itself under someone else's name. Fixed consistently
across all three the same session it was flagged, not left for the next person to notice.
todo.md gets updated at each real phase boundary — a module converted, a plugin written, a
bug fixed — rather than batched at the end of a session where drift is easy to lose track of.
Not every inconsistency gets self-caught before it ships. When one doesn't, the fix is disclosed the same way the original gaps are — including the fact that it took a second pair of eyes, not just the corrected result.
Where the real trade-offs got made, and written down
Not every decision has an obviously-correct answer, and pretending otherwise would just move the judgment
call somewhere less visible. This fork targets a Java 25 toolchain where upstream floors at 21 — a
deliberate choice that measurably raises the minimum JDK needed to consume this fork's published artifacts,
not a default left unexamined. Central Portal publishing (Sonatype's Maven Central replacement) has no
official Gradle plugin at all, so the community one used here (com.gradleup.nmcp) was verified
against its live documentation rather than assumed from training knowledge — which is exactly how gap #02
above got found in the first place. Both trade-offs are recorded in
todo.md
as deliberate choices, not oversights discovered later.