Skip to content

[#3195] feat(migration): OpenRewrite script to help with migration from Axon Framework 4.x to Axon(iq) Framework 5.x#4549

Merged
MateuszNaKodach merged 17 commits into
axon-5.1.xfrom
feat/5.1-openrewrite
May 19, 2026
Merged

[#3195] feat(migration): OpenRewrite script to help with migration from Axon Framework 4.x to Axon(iq) Framework 5.x#4549
MateuszNaKodach merged 17 commits into
axon-5.1.xfrom
feat/5.1-openrewrite

Conversation

@MateuszNaKodach
Copy link
Copy Markdown
Contributor

@MateuszNaKodach MateuszNaKodach commented May 7, 2026

Important for reviews

Don’t focus on the OpenRewrite scripts; instead, focus on the tests to verify whether they correctly modify the target project’s code.

How to run?

In order to test it on your own repository:

  • build Axon Framework 5 locally (because the migration module is not yet published)
  • build Axoniq Framework 5 locally

Execute in the repo to be migrated:

mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
  -Drewrite.recipeArtifactCoordinates=org.axonframework:axon-migration:5.1.1-SNAPSHOT \
  -Drewrite.activeRecipes=org.axonframework.migration.UpgradeAxon4ToAxoniq5

And be happy because of:

[WARNING] Estimate time saved: 26h 16m

If you find any places that weren’t migrated as expected by OpenRewrite, ask Claude to update the migration recipes and provide the AI agent with both the expected outcome and the mistake it made.


This PR will resolves issue #3195.

@MateuszNaKodach MateuszNaKodach added this to the Release 5.1.1 milestone May 7, 2026
@MateuszNaKodach MateuszNaKodach self-assigned this May 7, 2026
@MateuszNaKodach MateuszNaKodach requested a review from a team as a code owner May 7, 2026 14:23
@MateuszNaKodach MateuszNaKodach added Type: Feature Use to signal an issue is completely new to the project. Priority 2: Should High priority. Ideally, these issues are part of the release they’re assigned to. labels May 7, 2026
@MateuszNaKodach MateuszNaKodach requested review from hjohn, jangalinski and laura-devriendt-lemon and removed request for a team May 7, 2026 14:23
@MateuszNaKodach MateuszNaKodach changed the title feat(migration): OpenRewrite script to help with migration from Axon Framework 4.x to Axoniq Framework 5.x [#3195] feat(migration): OpenRewrite script to help with migration from Axon Framework 4.x to Axoniq Framework 5.x May 7, 2026
@MateuszNaKodach MateuszNaKodach force-pushed the feat/5.1-openrewrite branch from 54c81f6 to 056a3d1 Compare May 8, 2026 22:31
@MateuszNaKodach MateuszNaKodach requested review from hatzlj and smcvb May 8, 2026 22:31
…n 4.x to 5.x migration

- Add `ConfigureEventSourcedAnnotationKotlinTest` to verify Kotlin-specific behaviors in migration recipes.
- Introduce `ConvertCommandHandlerConstructorToCompanionObject` recipe to rewrite Kotlin `@CommandHandler` constructors into companion object methods meeting Axon 5.x requirements.
- Add `ConvertCommandHandlerConstructorToCompanionObjectTest` to validate the recipe.
- Add `MigrateAggregateTestFixtureSetupKotlinTest` to confirm Kotlin-compatible test fixture setup migration with proper class literal handling.
- Replace fully qualified references with simple imports for `@CommandHandler` and `EventAppender`.
- Update Kotlin migration recipes to ensure imports are correctly added for compatibility with Axon 5.x.
- Introduce Kotlin upgrade step in migration YAML to support dependency and plugin alignment with Kotlin 2.x.
- Improve handling of `CommandHandler` in companion object transformations to prevent import clashes during migration.
- Introduce `AddEventTagAnnotationKotlinTest` to validate annotation of Kotlin primary constructor parameters.
- Update `AddEventTagAnnotation` recipe to handle Kotlin-specific constructs such as `data class` primary constructors and `val/var` fields.
- Improve annotation handling for Kotlin sources to ensure `@EventTag` is correctly applied and formatted.
- Add test to validate fallback annotation placement for mismatched Kotlin field names.
- Refactor `AddEventTagAnnotation` to properly handle Kotlin `data class` field declaration order.
- Fix `isFirstFieldInClass` implementation to ensure correct field lookup in Kotlin and Java classes.
Closes the Gradle gap in `axon-migration` so the BOM rename + version
pin works end-to-end on Maven *and* Gradle projects (Groovy DSL,
Kotlin DSL, plain `platform(...)`, Spring Dependency Management
`mavenBom(...)`, and the Cinema-style `extra["axonFrameworkVersion"]`
+ `${property("...")}` indirection).

- Ship `migration/init.gradle` and document the Gradle invocation
  (`gradle rewriteRun --init-script ...`) in the module README.
- Compose `gradle.ChangeManagedDependency` and
  `gradle.ChangeExtraProperty` into the BOM YAMLs so Spring DM imports
  and Groovy `ext.*` version properties get rewritten alongside the
  existing Maven and `gradle.ChangeDependency` legs.
- Add `MigrateKotlinDslBomImport`, a small Kotlin-DSL-only recipe that
  fills two upstream gaps: `SpringDependencyManagementPluginEntry`
  doesn't extract version variables from `K.StringTemplate` whose
  embedded expression is `property("...")`, and `ExtraProperty` doesn't
  match the Kotlin-subscript `extra["..."] = "..."` shape. The recipe
  rewrites the `groupId:artifactId:` prefix in `mavenBom(...)` while
  preserving the `${property(...)}` indirection, and updates the
  matching `extra["..."]` literal in the same pass.
- Cover the new Gradle paths with `Axon4ToAxon5BomGradleTest` (Groovy
  + Kotlin DSL, both free and commercial legs, including the
  Cinema-style fixture). Tests attach a synthetic `GradleProject`
  marker rather than spinning up a Gradle daemon via `withToolingApi()`,
  keeping them lightweight (no `gradle-tooling-api` test dep needed).
- Expand annotation rewrites: add `@AggregateRoot` → `@EventSourcedEntity`, and clarify the removal of `@AggregateIdentifier` in favor of `tagKey` + `@EventTag`.
- Document unconditional removal of `@CreationPolicy`, including `AggregateCreationPolicy`, and explain handling for `CREATE_IF_MISSING` and `ALWAYS`.
- Provide additional guidance on lifted `@RoutingKey` and placement of `@EntityCreator` in constructors.
…nTestFixture` cleanup

- Introduce `AddAxonTestFixtureTearDown` recipe to automatically generate an `@AfterEach tearDown()` method calling `fixture.stop()`, ensuring proper lifecycle management for `AxonTestFixture` in JUnit 5 tests.
- Skip classes with existing `@AfterEach` methods or methods named `tearDown` to avoid duplication.
- Limit changes to Java sources only; Kotlin support to be addressed separately.
- Add comprehensive tests to verify correct migration behavior and idempotence.
Copy link
Copy Markdown
Contributor

@smcvb smcvb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What an insane effort to provide this. Thanks a ton, @MateuszNaKodach! I've skimmed the recipes and tests, and validated the larger Recipe implementation. The rough line is there and I am loving it. But I do have some concerns (as usual) to cover before we can merge this. I'd expect us to be close though! Lots of my points are questions we can potentially disregard.

Comment thread migration/pom.xml
Comment thread migration/init.gradle
Comment thread migration/src/main/resources/META-INF/rewrite/axon4-to-axoniq5-postgresql.yml Outdated
Comment thread migration/src/test/java/org/axonframework/migration/Axon4ToAxon5TestTest.java Outdated
Replace placeholder `@author Axon Framework Team` and `@since 5.2.0`
across all migration recipe classes with the correct values
(`@author Mateusz Nowak`, `@since 5.1.1`). Also renames the test class
`Axon4ToAxon5TestTest` to `Axon4ToAxon5TestFixtureTest` to read more
naturally (reviewer suggestion); the corresponding file move ships in
the same change so the new file is compilable at HEAD.
The Gradle init script was missing the standard Axon Framework header.
Aligns with the rest of the module.
…ming into umbrella

- `axon4-to-axon5-extension-tracing-opentelemetry.yml`: reduced to a
  placeholder no-op. Revisioning of the distributed-tracing extension
  is slated for a later release (issue #3594); doing the package move
  now would land ahead of the AF5 integration.
- `axon4-to-axoniq5-event-streaming.yml`: corrected the description.
  `MultiStreamableMessageSource` has existed since AF 4.2; the new
  Axoniq Framework 5 module consolidates that pattern rather than
  introducing a wholly new concept without an AF4 counterpart.
- `axon4-to-axoniq5-postgresql.yml`: removed entirely. The Axoniq
  PostgreSQL connector is new in AF5 with no AF4 source to rewrite,
  so an OpenRewrite recipe adds no value. README updated accordingly.
- `axon4-to-axoniq5.yml`: wired `Axon4ToAxoniq5EventStreaming` into the
  commercial umbrella so the placeholder runs in the right order once
  it gains class-level mappings.
…all sites

`AddEventTagAnnotation`'s scanner previously discovered event payload
types from `@EventSourcingHandler` method parameters only. That misses
a valid AF4 pattern: an event published via `AggregateLifecycle.apply`
but never re-sourced in the entity (no matching `@EventSourcingHandler`).
Without `@EventTag`, AF5 cannot determine the aggregate-id tag for that
event at runtime.

The scanner now also walks `AggregateLifecycle.apply(...)` invocations
anywhere in the entity body and adds the first argument's payload type
to the accumulator alongside the handler-based entries. The two sources
union into the same map, so events that are both applied AND sourced
remain handled exactly as before.

New test `Axon4ToAxon5EventSourcingTest#taggsEventPublishedViaApply\
EvenWithoutEventSourcingHandler` covers the apply-but-not-sourced case.
…ension tests

- BOM tests: bump sample starting version from 4.11.0 to 4.13.0 to
  match the current AF4 LTS line.
- Reactor / spring-aot extension tests: bump sample version to 4.12.0
  (the latest release of each extension on Maven Central).
- Micrometer extension test: replace `GlobalMetricRegistry` (which does
  not exist in AF5) with `MessageCountingMonitor` so the asserted
  package rename references a class that actually ships in the AF5
  metrics-micrometer module.
@MateuszNaKodach MateuszNaKodach requested a review from smcvb May 14, 2026 21:14
…chInterceptor` signatures

`MessageHandlerInterceptor.handle(UoW, InterceptorChain) -> Object` becomes
`interceptOnHandle(M, ProcessingContext, MessageHandlerInterceptorChain<M>)
-> MessageStream<?>` (and likewise for the dispatch interface). The method
body is intentionally left untouched: references to the dropped `unitOfWork`
/ `interceptorChain` / `messages` parameters turn into unresolved-symbol
compile errors, surfacing every call site that needs human review. A
class-level `// TODO #LLM` comment points to the migration path doc.

The signature swap goes through a sub-parsed stub class so the rewriter can
take real `J.MethodDeclaration` LST nodes for the new return type, name,
and parameter list. The unused `UnitOfWork` import is force-removed even
though the body still references its identifier — the body's `uow.X(...)`
is a compile error anyway, and the user's IDE will clean up further unused
imports during the manual body migration.

Lifecycle-hook semantics (`onCommit` vs `runOnAfterCommit`, no AF5
equivalent for `onRollback`) and the `interceptorChain.proceed()` → `chain
.proceed(message, context)` shift are intentionally NOT mechanized — a
blind rewrite would silently land semantically wrong code in non-trivial
interceptors.

Scoped to fire only on classes that actually carry the AF4 `handle` method,
so empty-body fixtures and abstract bases stay untouched.
Comment thread migration/pom.xml
Copy link
Copy Markdown
Contributor

@smcvb smcvb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concerns have been addressed, hence approving this PR. Some minor leftovers, but I trust you'll get them resolved.

…ework.migration namespace

- Rename the `name:` field in all 8 `axon4-to-axoniq5-*.yml` YAML recipes
  from `org.axonframework.migration.*` to `io.axoniq.framework.migration.*`
- Update the 7 recipeList cross-references in the umbrella UpgradeAxon4ToAxoniq5 recipe
- Relocate the 3 Axoniq-only test classes to `io/axoniq/framework/migration/` (git mv)
  and update their package declarations, scanRuntimeClasspath, and activateRecipes calls
- Update the two `Axon4ToAxon5Bom*Test` inline commercial-leg references
- Update recipe names in README.md examples and Group B table
- Update recipe name in init.gradle usage comment

Java source classes in org.axonframework.migration are not moved — they are
shared between the free AF5 and commercial Axoniq recipes.
…o5): pattern

Replace five ad-hoc TODO comment formats with a single grep-able tag that
marks compiler-transparent manual follow-up locations — spots where the recipe
made a best-effort change the compiler will not reject, but where developer
intervention is still required:
- Java/Kotlin line comment: // TODO(axon4to5): <action>
- Java/Kotlin inline:       /* TODO(axon4to5): <action> */
- Properties/YAML:          # TODO(axon4to5): <action>

Affected recipes: AnnotateObsoleteSequencingPolicyProperty,
ConfigureEventSourcedAnnotation, MigrateAggregateTestFixtureSetup,
MigrateMessageInterceptorSignatures, AddEventTagAnnotation.

Update README.md to document the pattern and the grep command so
developers know what to search for after running the migration.
@MateuszNaKodach MateuszNaKodach changed the title [#3195] feat(migration): OpenRewrite script to help with migration from Axon Framework 4.x to Axoniq Framework 5.x [#3195] feat(migration): OpenRewrite script to help with migration from Axon Framework 4.x to Axon(iq) Framework 5.x May 19, 2026
@sonarqubecloud
Copy link
Copy Markdown

@MateuszNaKodach MateuszNaKodach merged commit 5e83f7d into axon-5.1.x May 19, 2026
8 checks passed
@MateuszNaKodach MateuszNaKodach deleted the feat/5.1-openrewrite branch May 19, 2026 13:45
@smcvb smcvb mentioned this pull request May 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Priority 2: Should High priority. Ideally, these issues are part of the release they’re assigned to. Type: Feature Use to signal an issue is completely new to the project.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants