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
- Move Layer.register into the provider's prepare()/start() phase so it executes before CoreModule.notifyAfterCompleted() seals the registry
- For post-boot needs, use registerDynamic/unregisterDynamic which are explicitly allowed after sealing (per the javadoc on the seal method)
- In tests, register the layer before moduleManager.init()/notifyAfterCompleted(), or reset/avoid sealing in the test harness
- 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
- Register extension layers in ModuleProvider.prepare()/start(), never lazily on first data or in listeners
- Reserve static final Layer constants initialized at plugin class-load time
- In tests, register layers before moduleManager.notifyAfterCompleted() or design the test harness to skip sealing
- Post-boot registration must use registerDynamic/unregisterDynamic
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
- Zookeeper hostPort cannot be null or empty.
- Zookeeper namespace cannot be null or empty.
- No host setting.
- No port setting.
- Metric TTL should be at least 2 days, current value is {}
AI-assisted analysis of apache/skywalking@102af09b4a (2026-08-14).
Data as JSON: /api/errors/482e188dab8ef502.
Report an issue: GitHub.