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

  1. Choose an extension ordinal >= 1000 and coordinate between installed extensions (e.g. allocate a distinctive range like 1200, 1250, ...) to avoid mutual collisions
  2. 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
  3. Read the message to see which layer owns the ordinal, then pick a free one
  4. 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

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


AI-assisted analysis of apache/skywalking@102af09b4a (2026-08-14). Data as JSON: /api/errors/c4ac36a8a368f747. Report an issue: GitHub.