conductor-oss/conductor · error · IllegalArgumentException

cannot map non-scalar map key:

Error message

cannot map non-scalar map key: 

What it means

Thrown as IllegalArgumentException by MapType.getProtoType when the map's key type is not a ScalarType. Protobuf map fields only permit scalar keys (integral, bool, string), so a Map<CustomType, V> or Map<List, V> cannot be represented and protogen refuses to emit a proto map for it.

Source

Thrown at annotations-processor/src/main/java/com/netflix/conductor/annotationsprocessor/protogen/types/MapType.java:121

            method.endControlFlow();
            method.addStatement("to.$L($L)", javaMethodName("set", field), mapName);
        }
    }

    @Override
    public TypeName resolveJavaProtoType() {
        return ParameterizedTypeName.get(
                (ClassName) getRawJavaType(),
                getKeyType().getJavaProtoType(),
                getValueType().getJavaProtoType());
    }

    @Override
    public String getProtoType() {
        AbstractType keyType = getKeyType();
        AbstractType valueType = getValueType();
        if (!(keyType instanceof ScalarType)) {
            throw new IllegalArgumentException(
                    "cannot map non-scalar map key: " + this.getJavaType());
        }
        return String.format("map<%s, %s>", keyType.getProtoType(), valueType.getProtoType());
    }
}

View on GitHub (pinned to cf7c3e4a8a)

Solutions

  1. Change the map key to a scalar: Map<String, V>, Map<Integer, V>, Map<Long, V>, or Map<Boolean, V>.
  2. If a composite key is required, flatten it: convert Map<Composite,V> into a List<CompositeEntry> where CompositeEntry holds the key fields plus the value.
  3. Register a scalar mapping via TypeMapper.addScalarType only if the key is genuinely a primitive-like type supported by proto.

Example fix

// before
@ProtoMessage public class OrderBook {
    Map<Instrument, Integer> quantities; // Instrument is a message
}
// after
@ProtoMessage public class OrderBook {
    Map<String, Integer> quantitiesByInstrumentId;
}
Defensive patterns

Strategy: validation

Validate before calling

// Static design-time check: map keys must be scalar
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.Map;
static void assertScalarMapKeys(Class<?> msg) {
    for (Field f : msg.getDeclaredFields()) {
        if (Map.class.isAssignableFrom(f.getType())) {
            ParameterizedType pt = (ParameterizedType) f.getGenericType();
            Class<?> keyClass = (Class<?>) pt.getActualTypeArguments()[0];
            Set<Class<?>> scalars = Set.of(int.class, Integer.class, long.class, Long.class,
                String.class, boolean.class, Boolean.class);
            if (!scalars.contains(keyClass)) {
                throw new IllegalStateException("Map key of " + f + " is non-scalar: " + keyClass);
            }
        }
    }
}

Prevention

When it happens

Trigger: A @ProtoMessage class has a field of type Map<K,V> where K is a non-scalar Java type (another message, a collection, Object). TypeMapper builds a MapType, and when getProtoType() renders the field it hits the guard.

Common situations: Modeling domain objects with composite keys (e.g., Map<SomeEnum, V> if the enum is not registered as scalar, or Map<MyMessage, V>); introducing a new keyed collection that the codegen does not support.

Related errors


AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14). Data as JSON: /api/errors/eb9ef3f05b5c1b04. Report an issue: GitHub.