quarkusio/quarkus · error · IllegalArgumentException

Build item class must be leaf (final) types: %s

Error message

Build item class must be leaf (final) types: %s

What it means

BuildItem's constructor also requires every concrete build item class to be final (a leaf type). If getClass() reports a non-final class, it throws this IllegalArgumentException. Quarkus keys build steps on exact build item classes; subclassing an item would let one production be consumed as multiple item types, so the framework forbids it.

Source

Thrown at core/builder/src/main/java/io/quarkus/builder/item/BuildItem.java:22

/**
 * A build item which can be produced or consumed. Any item
 * which implements {@link AutoCloseable} will be automatically closed when the build
 * is completed, unless it is explicitly marked as a final build result in which case closure is
 * the responsibility of whomever invoked the build execution.
 * <p>
 * Resources should be fine-grained as possible, ideally describing only one aspect of the build process.
 */
public abstract class BuildItem {
    BuildItem() {
        final Class<? extends BuildItem> clazz = getClass();
        if (clazz.getTypeParameters().length != 0) {
            throw new IllegalArgumentException(
                    "A generic type is not allowed here; try creating a subclass with concrete type arguments instead: "
                            + getClass());
        }
        if (!Modifier.isFinal(clazz.getModifiers())) {
            throw new IllegalArgumentException("Build item class must be leaf (final) types: " + getClass());
        }
    }
}

View on GitHub (pinned to e1c734241f)

Solutions

  1. Mark the build item class final; if you need shared fields, extract them into a plain value object the item holds.
  2. Stop extending other BuildItems — define a new standalone final build item class instead.
  3. If you need polymorphic consumption, use a shared payload type or multiple distinct build items rather than a subclass hierarchy.
  4. Check any dynamically generated or proxied build item classes for the final modifier.

Example fix

// before
public class MyItem extends BuildItem { ... }

// after
public final class MyItem extends BuildItem { ... }
Defensive patterns

Strategy: type-guard

Validate before calling

static void assertFinalBuildItems(String packageName) {
    // e.g. via ClassGraph/Reflections scan in a unit test
    // for each Class<?> c in package: assert Modifier.isFinal(c.getModifiers())
}

Type guard

static boolean isLeafBuildItem(Class<?> clazz) {
    return BuildItem.class.isAssignableFrom(clazz)
        && Modifier.isFinal(clazz.getModifiers());
}

Try / catch

try {
    BuildItem item = createMyItem();
} catch (IllegalArgumentException e) {
    if (e.getMessage().startsWith("Build item class must be leaf")) {
        throw new IllegalStateException("Mark the build item final; do not subclass existing items", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Declaring `class MyBaseItem extends BuildItem` (non-final) and instantiating it or a subclass; extending an existing concrete build item to add fields instead of composing; instantiating an abstract-ish intermediate class that is not marked final.

Common situations: Inheritance-based reuse of build items when refactoring; attempts to specialize a framework-provided build item by subclassing; copy-pasted item classes missing the final modifier.

Related errors


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