aeron-io/aeron · error · ClusterException
incompatible app version
Error message
incompatible app version: ${SemanticVersion.toString(ctx.appVersion())} snapshot=${SemanticVersion.toString(appVersion)} What it means
Thrown as ClusterException when the appVersion stored in the loaded snapshot is not compatible (per the configured appVersionValidator) with the container's configured appVersion. It prevents loading snapshots written by an incompatible application version, guarding state format changes across releases.
Solutions
- Update the configured appVersion (aeron.cluster.app.version) or the appVersionValidator to accept the snapshot's version
- Write a fresh snapshot with the new application version before retiring the old one (take a snapshot before upgrade)
- Restore from a snapshot produced by a compatible version, or replay the log from an earlier point
- Implement SemanticVersion-based compatibility (same major, snapshot minor <= current) in your validator
Example fix
// before
container.context().appVersion(0x00010000); // 1.0, snapshot is 2.0
// after
container.context().appVersion(0x00020000)
.appVersionValidator((configVersion, snapshotVersion) ->
SemanticVersion.major(snapshotVersion) <= SemanticVersion.major(configVersion)); Defensive patterns
Strategy: validation
Validate before calling
int snapshotVersion = snapshotLoader.appVersion();
if (!ctx.appVersionValidator().isVersionCompatible(ctx.appVersion(), snapshotVersion)) {
throw new IllegalStateException("snapshot appVersion " + snapshotVersion + " incompatible with " + ctx.appVersion());
} Try / catch
try { loadSnapshot(in); } catch (ClusterException ex) { /* restore from a compatible snapshot instead */ } Prevention
- Set aeron.cluster.app.version on every release that changes service state format
- Always take a fresh snapshot with the new version before decommissioning old members
- Encode version compatibility (major-match) in a custom appVersionValidator and test upgrades
When it happens
Trigger: Restoring a cluster from a snapshot whose recorded appVersion fails ctx.appVersionValidator().isVersionCompatible(ctx.appVersion(), snapshotVersion); deploying a new release against old snapshots; changing the default appVersion without a compatible validator.
Common situations: Rolling upgrades/rollbacks where the old snapshot predates a state format change; forgetting to bump or set aeron.cluster.app.version; custom validator rejecting a minor-version mismatch.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- snapshot ended unexpectedly
- snapshot ended unexpectedly
- expected schemaId= , actual=
- unexpected snapshot type
- already in snapshot
AI-assisted analysis of aeron-io/aeron@6d60124e15 (2026-09-12).
Data as JSON: /api/errors/858f32ac871f0c42.
Report an issue: GitHub.
Appendix: source
Thrown at aeron-cluster/src/main/java/io/aeron/cluster/service/ClusteredServiceAgent.java:980
break;
}
if (0 == fragments)
{
archive.checkForErrorResponse();
if (image.isClosed())
{
throw new ClusterException("snapshot ended unexpectedly: " + image);
}
}
idle(fragments);
}
final int appVersion = snapshotLoader.appVersion();
if (!ctx.appVersionValidator().isVersionCompatible(ctx.appVersion(), appVersion))
{
throw new ClusterException(
"incompatible app version: " + SemanticVersion.toString(ctx.appVersion()) +
" snapshot=" + SemanticVersion.toString(appVersion));
}
timeUnit = snapshotLoader.timeUnit();
}
private long onTakeSnapshot(final long logPosition, final long leadershipTermId)
{
try (AeronArchive archive = AeronArchive.connect(ctx.archiveContext().clone());
ExclusivePublication publication = aeron.addExclusivePublication(
ctx.snapshotChannel(), ctx.snapshotStreamId()))
{
final String channel = ChannelUri.addSessionId(ctx.snapshotChannel(), publication.sessionId());
archive.startRecording(channel, ctx.snapshotStreamId(), LOCAL, true);
final CountersReader counters = aeron.countersReader();
final int counterId = awaitRecordingCounter(publication.sessionId(), counters, archive);
final long recordingId = RecordingPos.getRecordingId(counters, counterId);View on GitHub (pinned to 6d60124e15)