apache/pulsar · error · IllegalArgumentException

unsupported non-primitive Optional<%s> for %s

Error message

unsupported non-primitive Optional<%s> for %s

What it means

FieldParser.value() supports Optional<T> fields only when T is a simple (non-parameterized) type, because it converts the string with a single-class converter. If the Optional's type argument is itself parameterized (e.g. Optional<List<String>>, Optional<Map<K,V>>), it throws IllegalArgumentException. Note the message prints typeClazz.getClass() (the Type implementation class), not the type itself.

Source

Thrown at pulsar-common/src/main/java/org/apache/pulsar/common/util/FieldParser.java:191

        requireNonNull(field);
        // if field is not primitive type
        Type fieldType = field.getGenericType();
        if (fieldType instanceof ParameterizedType) {
            Class<?> clazz = (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
            if (field.getType().equals(List.class)) {
                // convert to list
                return stringToList(strValue, clazz);
            } else if (field.getType().equals(Set.class)) {
                // covert to set
                return stringToSet(strValue, clazz);
            } else if (field.getType().equals(Map.class)) {
                Class<?> valueClass =
                    (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[1];
                return stringToMap(strValue, clazz, valueClass);
            } else if (field.getType().equals(Optional.class)) {
                Type typeClazz = ((ParameterizedType) fieldType).getActualTypeArguments()[0];
                if (typeClazz instanceof ParameterizedType) {
                    throw new IllegalArgumentException(format("unsupported non-primitive Optional<%s> for %s",
                            typeClazz.getClass(), field.getName()));
                }
                @SuppressWarnings("unchecked") // typeClazz is verified to be a non-parameterized Class
                Optional<?> result = Optional.ofNullable(convert(strValue, (Class<?>) typeClazz));
                return result;
            } else {
                throw new IllegalArgumentException(
                        format("unsupported field-type %s for %s", field.getType(), field.getName()));
            }
        } else {
            return convert(strValue, field.getType());
        }
    }

    /**
     * Sets the empty/null value if field is allowed to be set empty.
     *
     * @param strValue

View on GitHub (pinned to 820761864e)

Solutions

  1. Change the field type to the plain generic collection (e.g. List<String> or Map<String,String>) which value() does support.
  2. Keep Optional<T> but make T a simple type (String, Integer, Long, Double, Float, Boolean, or enum).
  3. Handle such a field outside FieldParser: parse it manually with Jackson and set it via a setter instead of relying on update().

Example fix

// before
private Optional<List<String>> topicPatterns; // unsupported
// after
private List<String> topicPatterns; // or Optional<String> patternRegex
Defensive patterns

Strategy: validation

Validate before calling

static void checkOptionalField(Field f) {
    if (Optional.class.equals(f.getType())) {
        Type arg = ((ParameterizedType) f.getGenericType()).getActualTypeArguments()[0];
        if (arg instanceof ParameterizedType) {
            throw new IllegalArgumentException("Optional<" + arg + "> not supported for field " + f.getName());
        }
    }
}

Type guard

static boolean isSimpleOptional(Field f) {
    return Optional.class.equals(f.getType())
            && !(((ParameterizedType) f.getGenericType()).getActualTypeArguments()[0] instanceof ParameterizedType);
}

Try / catch

try {
    FieldParser.update(props, conf);
} catch (IllegalArgumentException e) {
    if (e.getMessage().startsWith("unsupported non-primitive Optional")) {
        throw new IllegalStateException("redefine field as plain or simple Optional type", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling update()/value() on a config class with a field declared as Optional<List<String>>, Optional<Map<String,String>>, or any Optional of a generic type, with a non-blank value present in the properties map.

Common situations: Adding an Optional-typed convenience field to a Pulsar configuration class with a nested generic; refactoring an existing List/Map field into Optional without realizing FieldParser cannot handle it.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/9c1008f5b43035e0. Report an issue: GitHub.