apache/skywalking · critical · IllegalStateException
Layer ordinal conflict at {}: existing={}, new={}
Error message
Layer ordinal conflict at {}: existing={}, new={} What it means
IllegalStateException thrown by Layer.register when the requested ordinal (value) is already taken by a different layer name. Ordinals are persisted with telemetry data, so they must be globally unique; the message names both the existing layer at that ordinal and the new name that attempted to claim it. The class javadoc recommends extensions use ordinals >= 1000 to avoid colliding with built-in layers.
Source
Thrown at oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/Layer.java:371
+ ". External layers must register before CoreModule.notifyAfterCompleted().");
}
if (name == null || !NAME_PATTERN.matcher(name).matches()) {
throw new IllegalArgumentException(
"Layer name must match [A-Z][A-Z0-9_]*: " + name);
}
final Layer existingByName = BY_NAME.get(name);
if (existingByName != null) {
if (existingByName.value == value && existingByName.isNormal == isNormal) {
return existingByName;
}
throw new IllegalStateException(
"Layer name conflict: " + name + " already registered as ordinal=" + existingByName.value
+ ", normal=" + existingByName.isNormal
+ "; refused re-registration as ordinal=" + value + ", normal=" + isNormal);
}
final Layer existingByValue = BY_VALUE.get(value);
if (existingByValue != null) {
throw new IllegalStateException(
"Layer ordinal conflict at " + value
+ ": existing=" + existingByValue.name + ", new=" + name);
}
final Layer layer = new Layer(name, value, isNormal);
BY_VALUE.put(value, layer);
BY_NAME.put(name, layer);
return layer;
}
/**
* Closes the registry. Subsequent {@link #register} calls throw; only
* {@link #registerDynamic} / {@link #unregisterDynamic} can mutate the registry after
* seal. Called by {@code CoreModuleProvider.notifyAfterCompleted()} after every
* module's prepare/start has run, so MAL/LAL/SPI/yaml all had their full window.
* Idempotent.
*/
public static synchronized void seal() {
rebuildCachedValues();View on GitHub (pinned to 102af09b4a)
Solutions
- Choose an extension ordinal >= 1000 and coordinate between installed extensions (e.g. allocate a distinctive range like 1200, 1250, ...) to avoid mutual collisions
- If the ordinal is meant to identify an existing layer, register the existing NAME with that exact value instead of a new name (identical name+value+isNormal is idempotent), or just reference the existing Layer constant
- Read the message to see which layer owns the ordinal, then pick a free one
- For shipped plugins, treat ordinal changes as breaking: keep the value stable across releases
Example fix
// before
Layer.register("MY_LAYER", 2, false); // 2 already used by a built-in layer -> throws
// after
Layer.register("MY_LAYER", 1200, false); // extension range >= 1000, unique Defensive patterns
Strategy: validation
Validate before calling
// coordinate ordinals: extensions should claim unique values >= 1000
private static final int MY_LAYER_ORDINAL = 1237;
if (Layer.valueOf(MY_LAYER_ORDINAL) != null) { // occupied
throw new IllegalStateException("Ordinal " + MY_LAYER_ORDINAL + " already claimed; allocate another >= 1000");
}
Layer.register("MY_LAYER", MY_LAYER_ORDINAL, false); Try / catch
try {
Layer.register(name, value, isNormal);
} catch (IllegalStateException e) {
if (e.getMessage().contains("ordinal conflict")) {
throw new IllegalStateException("Layer ordinal " + value + " taken by " + e.getMessage(), e);
}
throw e;
} Prevention
- Use ordinals >= 1000 for extensions as the class javadoc recommends
- Document an ordinal allocation table for all third-party extensions deployed in the same OAP
- Treat an extension's ordinal as frozen ABI once shipped — changing it breaks stored telemetry
- Prefer referencing an existing Layer constant when you meant a built-in layer instead of re-registering a new name at its value
When it happens
Trigger: Registering Layer.register("NEW_LAYER", 2, true) when ordinal 2 belongs to a built-in layer (e.g. MESH/database layers occupy low numbers); or two extensions both picking the same arbitrary value like 1000. BY_VALUE lookup detects the clash.
Common situations: Extension authors choosing small ordinals that collide with core layers; multiple third-party plugins in one OAP both hardcoding 1000; upgrading an extension whose ordinal now overlaps a newly added built-in layer.
Related errors
- Layer name conflict: {} already registered as ordinal={}, no
- Layer registry is sealed; cannot register {}={}. External la
- Layer name must match [A-Z][A-Z0-9_]*: {}
- Failed to compile hierarchy rule: {name}, expression: {expre
- Hierarchy rule parsing failed: {errors} in expression: {expr
AI-assisted analysis of apache/skywalking@102af09b4a (2026-08-14).
Data as JSON: /api/errors/c4ac36a8a368f747.
Report an issue: GitHub.