Distributed Tracing Migration
Axon Framework 5 rebuilds distributed tracing around a single SpanFactory abstraction.
The tracing Service Provider Interface (SPI) and every component decorator are part of the open-source framework modules. Exporting spans to OpenTelemetry backends is provided by Axoniq Framework through its Micrometer Tracing binding, which replaces the Axon Framework 4 axon-tracing-opentelemetry module.
The tracing API and the Micrometer binding are independent of Spring: applications can register tracing components through the Configuration API, while Spring Boot applications can rely on beans, auto-configuration, and properties.
This path covers the migration of tracing from Axon Framework 4 to Axon Framework 5. For a complete understanding of how tracing works in Axon Framework 5, see the Distributed Tracing reference documentation.
|
The most significant changes are:
|
Dependencies
Remove the Axon Framework 4 OpenTelemetry extension:
<dependency>
<groupId>org.axonframework</groupId>
<artifactId>axon-tracing-opentelemetry</artifactId>
<version>${axon-framework.version}</version>
</dependency>
The framework modules already contain their tracing decorators, so an application using a custom SpanFactory or the built-in LoggingSpanFactory needs no separate tracing module.
To export through the Axoniq Framework Micrometer binding, choose the setup matching the application’s configuration style:
-
Configuration API
-
Spring Boot
<dependency>
<groupId>io.axoniq.framework</groupId>
<artifactId>axoniq-tracing-micrometer</artifactId>
<version>${axoniq-framework.version}</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-logging-otlp</artifactId>
</dependency>
<dependency>
<groupId>io.axoniq.framework</groupId>
<artifactId>axoniq-tracing-micrometer</artifactId>
<version>${axoniq-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-logging-otlp</artifactId>
</dependency>
The bridge and exporter above are an OpenTelemetry example.
A Configuration API application creates the Micrometer Tracer and Propagator in its observability bootstrap.
Spring Boot Actuator can create them from the standard observability configuration.
Key changes:
-
The OpenTelemetry SDK is no longer wired by Axon Framework. The application configures the SDK, sampling, propagation, and exporters. Spring Boot applications can do this through the standard
management.tracing.andmanagement.otlp.tracing.properties. -
The component decorators themselves ship inside the regular framework modules (
axon-messaging,axon-modelling,axon-eventsourcing), so no dedicated tracing dependency is needed for them. -
Without the Axoniq Framework binding, the open-source modules provide
LoggingSpanFactory(SLF4J output) but no exporting implementation.
Configuring the span factory
In Axon Framework 4 you configured a SpanFactory explicitly.
In Axon Framework 5, register the active SpanFactory as a framework component.
A provider binding can build it from an application’s tracing SDK; with Spring Boot, a binding can consume the auto-configured tracing components.
The Axon Framework 4 registration looked like this:
public class AxonConfig {
public void configure(Configurer configurer) {
configurer.configureSpanFactory(
c -> OpenTelemetrySpanFactory.builder().build());
}
}
When registering a factory yourself in Axon Framework 5, use the configuration mechanism of the application:
-
Configuration API
-
Spring Boot
public AxonConfiguration configure(SpanFactory spanFactory) {
return MessagingConfigurer.create()
.componentRegistry(registry -> registry.registerComponent(
SpanFactory.class,
config -> spanFactory
))
.start();
}
@Configuration
public class TracingConfiguration {
@Bean
SpanFactory spanFactory() {
return LoggingSpanFactory.INSTANCE;
}
}
Key changes:
-
Configurer.configureSpanFactory(…)no longer exists. Register aSpanFactorycomponent through the Configuration API or expose one as a Spring bean. An application-supplied component takes precedence over a binding’s default. -
NoOpSpanFactoryhas been removed. When noSpanFactorycomponent is present, component decorators are not installed.
Multiple tracing destinations
In Axon Framework 4, applications composed several factories:
public SpanFactory spanFactory() {
return new MultiSpanFactory(Arrays.asList(
LoggingSpanFactory.INSTANCE,
OpenTelemetrySpanFactory.builder().build()
));
}
Key changes:
-
MultiSpanFactoryno longer exists. Fan-out moved to the tracing SDK’s export layer: configure one exporter per destination. Every Axon span is created and ended exactly once. -
The Axon Framework 4 pattern of combining
LoggingSpanFactorywith an exporting factory is replaced by combining a logging exporter with the backend exporter.
Configure the exporters through the configuration mechanism used by the application:
-
Configuration API
-
Spring Boot
public AxonConfiguration configureTracing() {
OtlpGrpcSpanExporter otlpExporter = OtlpGrpcSpanExporter.builder()
.setEndpoint("http://localhost:4317")
.build();
SpanExporter loggingExporter = OtlpJsonLoggingSpanExporter.create();
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(
BatchSpanProcessor.builder(otlpExporter).build()
)
.addSpanProcessor(
SimpleSpanProcessor.create(loggingExporter)
)
.build();
ContextPropagators contextPropagators =
ContextPropagators.create(W3CTraceContextPropagator.getInstance());
OpenTelemetrySdk openTelemetry = OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider)
.setPropagators(contextPropagators)
.build();
io.opentelemetry.api.trace.Tracer openTelemetryTracer =
openTelemetry.getTracer("AxoniqFramework");
Tracer tracer = new OtelTracer(
openTelemetryTracer,
new OtelCurrentTraceContext(),
event -> {
}
);
Propagator propagator = new OtelPropagator(contextPropagators, openTelemetryTracer);
return MessagingConfigurer.create()
.componentRegistry(registry -> registry
.registerComponent(Tracer.class, config -> tracer)
.registerComponent(Propagator.class, config -> propagator))
.lifecycleRegistry(registry -> registry.onShutdown(tracerProvider::close))
.start();
}
The OpenTelemetry SDK sends each completed span to the batch processor for the OpenTelemetry Protocol exporter and to
the simple processor for logging.
The Axoniq Framework Micrometer binding discovers the registered Micrometer Tracer and Propagator components and
provides Axon’s SpanFactory.
The lifecycle handler closes the SdkTracerProvider during application shutdown so that pending batches are flushed.
@Configuration(proxyBeanMethods = false)
public class MultipleExportersConfiguration {
@Bean
OtlpGrpcSpanExporter otlpSpanExporter() {
return OtlpGrpcSpanExporter.builder()
.setEndpoint("http://localhost:4317")
.build();
}
@Bean
SpanExporter loggingSpanExporter() {
return OtlpJsonLoggingSpanExporter.create();
}
}
Spring Boot collects all SpanExporter beans and fans completed spans out through its auto-configured span processor.
The example declares both exporters as beans; alternatively, let management.otlp.tracing.* configure the
OpenTelemetry Protocol exporter and declare only the logging exporter bean.
Exporter beans, SDK builders, profiles, and external configuration can enable different destinations per environment.
Axon Framework still sees one SpanFactory; it does not create a second Axon span for each exporter.
Span names
Operation spans adopt the OpenTelemetry <operation> <target> naming convention, where the target is the message’s qualified name.
Update dashboards, alerts, and sampling rules that match on Axon Framework 4 span names.
| Axon Framework 4 span | Axon Framework 5 span |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Removed |
|
|
|
|
|
|
|
Unchanged, and no longer limited to Spring Boot applications |
Span attribute keys
The built-in attribute providers moved from underscore keys to the OpenTelemetry-style dotted layout:
| Axon Framework 4 key | Axon Framework 5 key |
|---|---|
|
|
|
|
|
|
|
|
(not present) |
|
Tracing settings
Tracing behavior is configured through framework components.
Configuration API applications register MessagingTracingSettings, ModellingTracingSettings, or EventSourcingTracingSettings.
Spring Boot maps the axon.tracing.* properties to those same settings components.
-
Configuration API
-
Spring Boot
Register only the settings records that need values different from their enabledByDefault() defaults:
public void configureTracingSettings(MessagingConfigurer configurer) {
MessagingTracingSettings tracingSettings = MessagingTracingSettings.enabledByDefault()
.withEventSourcingHandlersEnabled(true)
.withEventProcessorDistributedInSameTrace(
true
)
.withEventProcessorDistributedInSameTraceTimeLimit(
Duration.ofMinutes(1)
);
configurer.componentRegistry(registry -> registry.registerComponent(
MessagingTracingSettings.class,
config -> tracingSettings
));
}
The three records control the components in their respective framework modules.
Their nested SpanAttributesProviders records control the built-in attribute providers.
The Axon Framework 4 property names map to the following Axon Framework 5 properties:
| Axon Framework 4 property | Axon Framework 5 equivalent |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
Removed: a distributed command handler always continues the dispatcher’s trace |
|
Removed: a distributed query handler always continues the dispatcher’s trace |
|
|
|
|
|
|
|
Removed: snapshot spans nest under the entity’s trace through the |
|
Removed: deadlines and sagas are not available in Axon Framework 5.0 |
(not present) |
Per-component toggles such as |
Trace topology changes
The relationships between spans changed in ways that improve navigability:
-
Distributed commands and queries always continue the dispatcher’s trace. The caller is actively waiting, so the whole request-response path renders as one trace. The Axon Framework 4 opt-out no longer exists.
-
Consumers with streaming execution semantics, including pooled streaming processors and Persistent Stream configurations, now parent each
EventProcessor.processspan under theStreamingEventProcessor.batchspan that executes it, with a span link back to the publisher. In Axon Framework 4 the batch span was a separate, childless trace. The publisher relationship stays a link because a batch can carry events from several publishers, and an event handled long after publication (catch-up, replay) must not stretch the publisher’s finished trace. -
Same-trace mode for event processors works as before: fresh events continue the publisher’s trace, guarded by
distributed-in-same-trace-time-limit; stale events start a linked trace. -
Snapshot operations are children of the entity’s trace instead of the Axon Framework 4
separateTracechoice, so slow snapshot loads are visible inside command handling. -
Subscription query updates produce a producer span on the emitting node and a consumer span on the subscribing node with a span link back to the originating subscription query.
Custom SpanFactory implementations
Implementing a custom SpanFactory (against a tracing library other than Micrometer) remains possible: the interface lives in org.axonframework.messaging.tracing in axon-messaging, and a registered SpanFactory component takes precedence.
Most applications never need this. The Micrometer binding can use OpenTelemetry and Brave bridges in plain Java or in a Spring-managed application.