quarkusio/quarkus · error · IllegalStateException

Improper registration of ZoneId substitution

Error message

Improper registration of ZoneId substitution

What it means

AdditionalSubstitutionsBuildStep registers bytecode-recorder substitutions for JDK time types. It reflects on java.time.ZoneId/ZoneRegion/ZoneOffset; if any of these classes cannot be loaded on the JVM running the build, registration is incomplete and it throws IllegalStateException('Improper registration of ZoneId substitution').

Source

Thrown at core/deployment/src/main/java/io/quarkus/deployment/recording/substitutions/AdditionalSubstitutionsBuildStep.java:31

    public void additionalSubstitutions(BuildProducer<ObjectSubstitutionBuildItem> producer) {
        zoneIdSubstitutions(producer);
    }

    @SuppressWarnings("unchecked")
    private void zoneIdSubstitutions(BuildProducer<ObjectSubstitutionBuildItem> producer) {
        try {
            /*
             * We can't refer to these classes as they are package private but we need a handle on need
             * because the bytecode recorder needs to have the actual class registered and not a super class
             */

            Class<ZoneId> zoneRegionClass = (Class<ZoneId>) Class.forName("java.time.ZoneRegion");
            producer.produce(new ObjectSubstitutionBuildItem(zoneRegionClass, String.class, ZoneIdSubstitution.class));

            Class<ZoneId> zoneOffsetClass = (Class<ZoneId>) Class.forName("java.time.ZoneOffset");
            producer.produce(new ObjectSubstitutionBuildItem(zoneOffsetClass, String.class, ZoneIdSubstitution.class));
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException("Improper registration of ZoneId substitution", e);
        }
    }
}

View on GitHub (pinned to e1c734241f)

Solutions

  1. Build with a complete standard JDK 11+ that includes full java.time in java.base
  2. Check the JDK with: jshell> Class.forName("java.time.ZoneRegion") — it must succeed
  3. Stop excluding/stripping java.time from your JDK or jlink image
  4. Point JAVA_HOME / Maven toolchains to a standard Temurin/Oracle/OpenJDK build

Example fix

// before (CI image)
FROM eclipse-temurin:11-jre-alpine-stripped // java.time partially removed
// after
FROM eclipse-temurin:11-jdk // full java.base module
JAVA_HOME=/usr/lib/jvm/temurin-11-jdk-amd64
Defensive patterns

Strategy: validation

Validate before calling

// run before building on a new JDK image
Class.forName("java.time.ZoneRegion");
Class.forName("java.time.ZoneOffset"); // must not throw ClassNotFoundException

Prevention

When it happens

Trigger: Class.forName('java.time.ZoneRegion') or 'java.time.ZoneOffset' throws ClassNotFoundException during augmentation — only possible on a non-standard/broken JDK (pre-Java-8 java.time, stripped JRE modules such as missing java.base entries, exotic runtimes).

Common situations: Building on a stripped-down JVM/JDK image missing java.time classes; using an unusual JDK vendor or a jlink-minimized runtime that removed parts of java.base; misconfigured toolchains in CI.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/d32a3f100e54c86b. Report an issue: GitHub.