apache/beam · error · IllegalStateException

cannot return null keyCoder

Error message

cannot return null keyCoder

What it means

getKeyCoder() on a KeyedPCollectionTuple returns the cached keyCoder, but if the tuple was constructed without one (null) and no input has forced inference yet, it refuses to return null and throws an IllegalStateException.

Solutions

  1. Ensure at least one input PCollection with a KvCoder was added before calling getKeyCoder()
  2. Construct the KeyedPCollectionTuple with an explicit key coder (e.g. KeyedPCollectionTuple.of creates it from the first input)
  3. Guard the call with keyCoder null-check via getCoGbkResultSchema/getPipeline introspection instead

Example fix

// before
Coder<K> kCoder = tuple.getKeyCoder(); // NPE-style state error if empty
// after
if (!tuple.isEmpty()) {
  Coder<K> kCoder = tuple.getKeyCoder();
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (tuple.isEmpty()) { throw new IllegalStateException("Cannot get key coder from empty KeyedPCollectionTuple"); }

Try / catch

try { return tuple.getKeyCoder(); } catch (IllegalStateException e) { return inferFromFirstInput(tuple); }

Prevention

When it happens

Trigger: Calling getKeyCoder() (or keyCoder-through paths) on a KeyedPCollectionTuple whose keyCoder field is null because no PCollection coder was consulted yet, e.g. on an empty or not-fully-built tuple.

Common situations: Inspecting the key coder before applying the transform; a zero-input tuple passed around for testing; framework code reading keyCoder on a partially constructed tuple.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/3eaea31a4eeea4c1. Report an issue: GitHub.

Appendix: source

Thrown at sdks/java/core/src/main/java/org/apache/beam/sdk/transforms/join/KeyedPCollectionTuple.java:141

   * Expands the component {@link PCollection PCollections}, stripping off any tag-specific
   * information.
   */
  @Override
  public Map<TupleTag<?>, PValue> expand() {
    ImmutableMap.Builder<TupleTag<?>, PValue> retval = ImmutableMap.builder();
    for (TaggedKeyedPCollection<K, ?> taggedPCollection : keyedCollections) {
      retval.put(taggedPCollection.tupleTag, taggedPCollection.pCollection);
    }
    return retval.build();
  }

  /**
   * Returns the key {@link Coder} for all {@link PCollection PCollections} in this {@link
   * KeyedPCollectionTuple}.
   */
  public Coder<K> getKeyCoder() {
    if (keyCoder == null) {
      throw new IllegalStateException("cannot return null keyCoder");
    }
    return keyCoder;
  }

  /** Returns the {@link CoGbkResultSchema} associated with this {@link KeyedPCollectionTuple}. */
  public CoGbkResultSchema getCoGbkResultSchema() {
    return schema;
  }

  @Override
  public Pipeline getPipeline() {
    return pipeline;
  }

  private static <K, V> Coder<K> getKeyCoder(PCollection<KV<K, V>> pc) {
    // TODO: This should already have run coder inference for output, but may not have been consumed
    // as input yet (and won't be fully specified); This is fine

View on GitHub (pinned to 12126d8942)