eclipse-vertx/vert.x · error · IllegalArgumentException

Cannot put null in key or value of async map

Error message

Cannot put null in key or value of async map

What it means

SharedDataImpl.checkType rejects null keys or values before they reach an async map: async maps cannot store null entries because keys/values must be serializable and Comparable across the cluster. It throws IllegalArgumentException with this message.

Source

Thrown at vertx-core/src/main/java/io/vertx/core/shareddata/impl/SharedDataImpl.java:142

  @SuppressWarnings("unchecked")
  @Override
  public <K, V> Future<AsyncMap<K, V>> getLocalAsyncMap(String name) {
    LocalAsyncMapImpl<K, V> asyncMap = (LocalAsyncMapImpl<K, V>) localAsyncMaps.computeIfAbsent(name, n -> new LocalAsyncMapImpl<>(vertx));
    ContextInternal context = vertx.getOrCreateContext();
    return context.succeededFuture(new WrappedAsyncMap<>(asyncMap));
  }

  @Override
  public Future<Counter> getLocalCounter(String name) {
    Counter counter = localCounters.computeIfAbsent(name, n -> new AsynchronousCounter(vertx));
    ContextInternal context = vertx.getOrCreateContext();
    return context.succeededFuture(counter);
  }

  private static void checkType(Object obj) {
    if (obj == null) {
      throw new IllegalArgumentException("Cannot put null in key or value of async map");
    }
    // All immutables and byte arrays are Serializable by the platform
    if (!(obj instanceof Serializable || obj instanceof ClusterSerializable)) {
      throw new IllegalArgumentException("Invalid type: " + obj.getClass().getName() + " to put in async map");
    }
  }

  public static final class WrappedAsyncMap<K, V> implements AsyncMap<K, V> {

    private final AsyncMap<K, V> delegate;

    WrappedAsyncMap(AsyncMap<K, V> other) {
      this.delegate = other;
    }

    @Override
    public Future<V> get(K k) {
      checkType(k);

View on GitHub (pinned to fb308bd8c3)

Solutions

  1. Check key/value for null before calling any map method and handle the null case explicitly.
  2. Provide a non-null default or sentinel value instead of storing null.
  3. Fix the upstream producer so the value is never null (Objects.requireNonNull at creation).

Example fix

// before
String val = config.getString("opt");
map.put("k", val); // NPE risk / IllegalArgumentException when val == null
// after
String val = config.getString("opt");
if (val != null) map.put("k", val);
Defensive patterns

Strategy: validation

Validate before calling

if (key == null || value == null) {
  throw new IllegalArgumentException("async map key/value must be non-null");
}

Type guard

static <V> java.util.Optional<V> nonNullValue(V v) { return java.util.Optional.ofNullable(v); }

Try / catch

try {
  map.put(key, value);
} catch (IllegalArgumentException e) {
  if (e.getMessage().contains("Cannot put null")) {
    map.put(key, defaultValue);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling put/putIfAbsent/get/replace on an AsyncMap or cluster-wide map with a null key or null value.

Common situations: A lookup returned null and its result was passed straight to map.put; optional config values (null when unset) used as values; deserialized JSON fields defaulting to null.

Related errors


AI-assisted analysis of eclipse-vertx/vert.x@fb308bd8c3 (2026-09-06). Data as JSON: /api/errors/e4da922c5e096e04. Report an issue: GitHub.