quarkusio/quarkus · error · IllegalArgumentException

The configuration ${clazz} must be an interface annotated wi

Error message

The configuration ${clazz} must be an interface annotated with @ConfigRoot and @ConfigMapping

What it means

BuildTimeConfigurationReader.collectConfigRoots loads classes listed in the generated config-roots list and requires each to be an interface annotated with @ConfigRoot (and @ConfigMapping). A non-interface class in that list violates the configuration model contract, so IllegalArgumentException is thrown at build start.

Source

Thrown at core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java:59

import io.smallrye.config.SmallRyeConfig;
import io.smallrye.config.SmallRyeConfigBuilder;
import io.smallrye.config.SmallRyeConfigBuilderCustomizer;
import io.smallrye.config.SysPropConfigSource;
import io.smallrye.config.common.AbstractConfigSource;

/**
 * A configuration reader.
 */
public final class BuildTimeConfigurationReader {
    private static final String CONFIG_ROOTS_LIST = "META-INF/quarkus-config-roots.list";

    private static List<Class<?>> collectConfigRoots(ClassLoader classLoader) throws IOException, ClassNotFoundException {
        Assert.checkNotNullParam("classLoader", classLoader);
        // populate with all known types
        List<Class<?>> roots = new ArrayList<>();
        for (Class<?> clazz : ServiceUtil.classesNamedIn(classLoader, CONFIG_ROOTS_LIST)) {
            if (!clazz.isInterface()) {
                throw new IllegalArgumentException(
                        "The configuration " + clazz + " must be an interface annotated with @ConfigRoot and @ConfigMapping");
            }

            ConfigRoot configRoot = clazz.getAnnotation(ConfigRoot.class);
            if (configRoot == null) {
                throw new IllegalArgumentException("The configuration " + clazz + " is missing the @ConfigRoot annotation");
            }

            ConfigMapping configMapping = clazz.getAnnotation(ConfigMapping.class);
            if (configMapping == null) {
                throw new IllegalArgumentException("The configuration " + clazz + " is missing the @ConfigMapping annotation");
            }

            roots.add(clazz);
        }
        return roots;
    }

View on GitHub (pinned to e1c734241f)

Solutions

  1. Convert the config root to a Java interface annotated with @ConfigMapping and @ConfigRoot
  2. Remove any leftover legacy @ConfigRoot/@ConfigItem class-based configuration
  3. Rebuild so the generated config-roots service file no longer lists the offending class

Example fix

// before
@ConfigRoot(name="app")
public class AppConfig { @ConfigItem public String name; }
// after
@ConfigRoot(name="app")
@ConfigMapping(prefix="app")
public interface AppConfig { String name(); }
Defensive patterns

Strategy: validation

Validate before calling

Class<?> c = ...;
if (!c.isInterface() || c.getAnnotation(ConfigRoot.class) == null || c.getAnnotation(ConfigMapping.class) == null)
    throw new IllegalStateException(c + " must be an @ConfigMapping interface annotated with @ConfigRoot");

Type guard

boolean isConfigRootInterface(Class<?> c) { return c.isInterface() && c.isAnnotationPresent(ConfigRoot.class); }

Try / catch

try { new BuildTimeConfigurationReader(classLoader); } catch (IllegalArgumentException e) { log.error("Bad config root registration", e); throw e; }

Prevention

When it happens

Trigger: A class implementing/registered as a config root that is a class or enum rather than an interface; stale generated CONFIG_ROOTS_LIST service file referencing a legacy class-based config root.

Common situations: Migrating from legacy @ConfigRoot class + @ConfigItem style (removed in modern Quarkus) to @ConfigMapping interfaces; a custom extension still registering an old-style config class.

Related errors


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