Portfolio Manifest — Prepared 2026-08-23

Terrence Daniels

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.

1 of 14reactor modules fully converted
34commits, 2 days
3build-logic convention plugins done
8real gaps found & fixed
17common source files converted
6public surfaces kept in sync

Why this project

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.

Gradle 9.2 Java 25 toolchain Kotlin DSL JUnit 6 Mockito Checkstyle com.gradleup.nmcp Central Portal

Build system first, real source now underway

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.

Verification over assumption

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.

Gaps disclosed, not hidden

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.

Eight real gaps, found and fixed

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.

01
testImplementation(project(":test-logging")) — a circular dependency Gradle would build without complaint
Maven's 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.
high · build-breaking
02
nmcp 1.6.1 — configuration cache crashes on providers.environmentVariable()
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.
high · undocumented plugin interaction
03
build/ — simultaneously real content and Gradle's own output directory
The root 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.
medium · gitignore drift
04
One missing module directory blocked the entire build's configuration, not just itself
Every 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.
high · build-breaking
05
Root pom.xml's Javadoc-skip list names four modules that don't exist in this reactor
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.
low · upstream config drift
06
Published artifacts were silently getting the wrong Maven coordinate
Every Axon module's real artifactId is 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.
high · silent publish-coordinate bug
07
Gradle's javadoc task rejected Axon's own HTML outright
The moment 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.
high · build-breaking
08
The CI workflow's own first run failed in 15 seconds
gradle-version: '9.2' in the new build-verification workflow failed immediately with Error: Gradle version 9.2 does not existgradle/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.
medium · CI misconfiguration
Honest gap, tracked not solved: the Gradle wrapper still isn't generated in this repo — building requires a system-installed Gradle rather than ./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.

Keeping the record honest across six surfaces

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.

Per-checkpoint discipline

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.

Caught by review, not hidden either way

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.

Judgment calls

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.