quarkusio/quarkus · error · UnsupportedOperationException

Unsupported wildcard type: ${wildcard}

Error message

Unsupported wildcard type: ${wildcard}

What it means

When recording a java.lang.reflect.WildcardType parameter, Quarkus can emit lower-bounded wildcards (? super X); if the wildcard has no lower bound in the branch being handled (e.g. only an upper bound '? extends X' is unsupported here), it throws UnsupportedOperationException. This is a limitation of the recorder's type-parameter recording support.

Source

Thrown at core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java:843

                    @Override
                    ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                        return method.invokeStaticMethod(ofMethod(WildcardTypeImpl.class, "withUpperBound",
                                WildcardType.class, java.lang.reflect.Type.class), context.loadDeferred(res));
                    }
                };
            } else if (lowerBound.length == 1) {
                // lower bound
                DeferredParameter res = loadObjectInstance(lowerBound[0], existing,
                        java.lang.reflect.Type.class, relaxedValidation);
                return new DeferredParameter() {
                    @Override
                    ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                        return method.invokeStaticMethod(ofMethod(WildcardTypeImpl.class, "withLowerBound",
                                WildcardType.class, java.lang.reflect.Type.class), context.loadDeferred(res));
                    }
                };
            } else {
                throw new UnsupportedOperationException("Unsupported wildcard type: " + wildcard);
            }
        } else if (expectedType == boolean.class || expectedType == Boolean.class || param instanceof Boolean) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.load((boolean) param);
                }
            };
        } else if (expectedType == int.class || expectedType == Integer.class || param instanceof Integer) {
            return new DeferredParameter() {
                @Override
                ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                    return method.load((int) param);
                }
            };
        } else if (expectedType == short.class || expectedType == Short.class || param instanceof Short) {
            return new DeferredParameter() {
                @Override

View on GitHub (pinned to e1c734241f)

Solutions

  1. Avoid passing raw WildcardType objects to recorders; resolve the wildcard to its concrete upper bound (e.g. via quarkus-extension's ReflectUtil or GAST types) before recording
  2. Use parameterized Class/TypeToken-style concrete types instead of wildcards
  3. Check Quarkus version — support for wildcard bounds has changed; upgrade to a version handling your wildcard shape
  4. If you control the type source, replace '? extends X' declarations with the concrete type X
  5. File an issue if your wildcard type should be supported (lower-bound wildcards are handled)

Example fix

// before
Type t = field.getGenericType(); // contains '? extends Foo'
recorder.setType(t);
// after
Type t = resolveWildcardToUpperBound(field.getGenericType()); // yields Foo.class
recorder.setType(t);
Defensive patterns

Strategy: type-guard

Validate before calling

static boolean isRecorderSafeType(java.lang.reflect.Type t) {
    if (t instanceof java.lang.reflect.WildcardType w) {
        return w.getLowerBounds().length > 0; // only lower-bounded wildcards are supported
    }
    return true;
}

Type guard

static boolean isWildcardWithLowerBound(java.lang.reflect.Type t) {
    return t instanceof java.lang.reflect.WildcardType w && w.getLowerBounds().length > 0;
}

Try / catch

try {
    recorder.setType(genericType);
} catch (UnsupportedOperationException e) {
    if (e.getMessage().startsWith("Unsupported wildcard")) {
        recorder.setType(resolveToUpperBound(genericType));
    } else { throw e; }
}

Prevention

When it happens

Trigger: Passing a reflectively-obtained Type that is a WildcardType with an upper bound only (e.g. '? extends Foo'), or an unbounded '?' not covered by the handled cases, into a recorder method parameter of type Type.

Common situations: Extension code passing generic reflection metadata (from ParameterizedType fields or generated config mappings) into recorders; using types captured from user classes with '? extends' bounds.

Related errors


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