apache/skywalking · critical · IllegalStateException

Layer registry is sealed; cannot register {}={}. External la

Error message

Layer registry is sealed; cannot register {}={}. External layers must register before CoreModule.notifyAfterCompleted().

What it means

IllegalStateException thrown by Layer.register(name, value, isNormal) when the static layer registry has already been sealed (CoreModule.notifyAfterCompleted() sets SEALED). The registry seals after OAP boot so layer ordinals stay stable for stored data; any extension trying to register a new layer afterwards is rejected with instructions in the message itself.

Source

Thrown at oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/Layer.java:351

    }

    /**
     * Single registration path used by both the built-in static initializer above and by every
     * external source ({@code LayerExtensionLoader} for operator yaml + SPI, and the MAL/LAL DSL
     * loaders parsing inline {@code layerDefinitions:} blocks). Idempotent on identical
     * re-registration so the same extension loaded by multiple paths is harmless.
     *
     * @param name    upper-snake-case identifier; must match {@code [A-Z][A-Z0-9_]*}
     * @param value   ordinal unique across all layers (see class javadoc for the ordinal
     *                conventions and the {@code >= 1000} recommendation for extensions)
     * @param isNormal whether services in this layer are agent-installed (true) or conjectured (false)
     * @return the registered layer
     * @throws IllegalStateException     if the registry is sealed, or on a name/ordinal conflict
     * @throws IllegalArgumentException  if name shape is invalid
     */
    public static synchronized Layer register(final String name, final int value, final boolean isNormal) {
        if (SEALED) {
            throw new IllegalStateException(
                "Layer registry is sealed; cannot register " + name + "=" + value
                    + ". 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);

View on GitHub (pinned to 102af09b4a)

Solutions

  1. Move Layer.register into the provider's prepare()/start() phase so it executes before CoreModule.notifyAfterCompleted() seals the registry
  2. For post-boot needs, use registerDynamic/unregisterDynamic which are explicitly allowed after sealing (per the javadoc on the seal method)
  3. In tests, register the layer before moduleManager.init()/notifyAfterCompleted(), or reset/avoid sealing in the test harness
  4. Check the exception message: it names the layer and ordinal you tried, confirming which registration raced the seal

Example fix

// before (registered too late — first request)
public class MyListener implements SpanListener {
  private static final Layer MY_LAYER = Layer.register("MY_LAYER", 1200, false); // after seal -> throws
}

// after (registered at provider start, before core seals)
public class MyModuleProvider extends ModuleProvider {
  public void prepare() {
    Layer.register("MY_LAYER", 1200, false); // boot-time, registry open
  }
}
Defensive patterns

Strategy: validation

Validate before calling

static final Layer MY_LAYER = Layer.register("MY_LAYER", 1200, false); // class-load / prepare()-time
// guard any late path:
if (!Layer.listAllLayers().stream().anyMatch(l -> l.name().equals("MY_LAYER"))) {
    Layer.registerDynamic("MY_LAYER", 1200, false); // post-seal API
}

Try / catch

try {
    Layer.register(name, value, isNormal);
} catch (IllegalStateException e) {
    if (e.getMessage().contains("sealed")) {
        Layer.registerDynamic(name, value, isNormal); // allowed after seal
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling Layer.register after the OAP core module finished booting — e.g. a receiver/analyzer plugin registering its custom layer from start() code that runs after CoreModule.notifyAfterCompleted(), a dynamically loaded extension, or application code registering lazily on first data.

Common situations: Writing a custom receiver plugin that adds a layer (e.g. MESH or a proprietary tier) but performs registration late in the module lifecycle; OAL/observer code that defers registration until first telemetry; unit tests that boot the full module manager before registering test layers.

Related errors


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