quarkusio/quarkus · error · IllegalArgumentException

Type + type.name().toString() + which is used in class +

Error message

Type  + type.name().toString() +  which is used in class  + declaringClass +  must define a generic argument

What it means

determineSingleGenericType extracts the single generic argument of a config property type. If the type is not a parameterized type at all (no generic argument declared), it throws IllegalArgumentException 'Type <type> which is used in class <class> must define a generic argument'. Called from the mandatory and optional value read generators for types that need one type parameter (e.g. List, Optional).

Source

Thrown at extensions/spring-boot-properties/deployment/src/main/java/io/quarkus/spring/boot/properties/deployment/ConfigurationPropertiesUtil.java:188

            return false;
        }
        return !parameterizedType.arguments().get(0).name().toString().startsWith("java");
    }

    private static boolean isCollection(final Type resultType) {
        return DotNames.COLLECTION.equals(resultType.name()) ||
                DotNames.LIST.equals(resultType.name()) ||
                DotNames.SET.equals(resultType.name());
    }

    private static boolean isMap(final Type resultType) {
        return DotNames.MAP.equals(resultType.name()) ||
                DotNames.HASH_MAP.equals(resultType.name());
    }

    static Type determineSingleGenericType(Type type, DotName declaringClass) {
        if (!(type.kind() == Type.Kind.PARAMETERIZED_TYPE)) {
            throw new IllegalArgumentException("Type " + type.name().toString() + " which is used in class " + declaringClass
                    + " must define a generic argument");
        }

        ParameterizedType parameterizedType = type.asParameterizedType();
        if (parameterizedType.arguments().size() != 1) {
            throw new IllegalArgumentException("Type " + type.name().toString() + " which is used in class " + declaringClass
                    + " must define a single generic argument");
        }

        return type.asParameterizedType().arguments().get(0);
    }

    static void registerImplicitConverter(Type type, BuildProducer<ReflectiveClassBuildItem> reflectiveClasses) {
        // We need to register for reflection in case an implicit converter is required.
        if (!MicroProfileConfigProcessor.isHandledByProducers(type)) {
            if (type.kind() != Type.Kind.ARRAY) {
                reflectiveClasses
                        .produce(ReflectiveClassBuildItem.builder(type.name().toString()).methods().build());

View on GitHub (pinned to e1c734241f)

Solutions

  1. Add the type argument: List<String>, Optional<Duration>, etc.
  2. Ensure interface-based config property getters declare concrete generic return types.
  3. Avoid custom generic wrappers; only single-argument parameterized types are supported here.

Example fix

// before
List getHosts();

// after
List<String> getHosts();
Defensive patterns

Strategy: type-guard

Type guard

static boolean hasSingleGenericArg(java.lang.reflect.Type t) {
  return t instanceof java.lang.reflect.ParameterizedType pt
      && pt.getActualTypeArguments().length == 1;
}

Prevention

When it happens

Trigger: A @ConfigurationProperties field/getter uses a generic container such as List, Set, or Optional as a raw type (Kind != PARAMETERIZED_TYPE); raised while generating config population code invoked via createReadMandatoryValueAndConvertIfNeeded / createReadOptionalValueAndConvertIfNeeded.

Common situations: Raw List instead of List<String>; generics omitted on an interface getter; legacy code using raw collection types.

Related errors


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