apache/beam · error · CoderException

cannot encode a null ReadableDuration

Error message

cannot encode a null ReadableDuration

What it means

DurationCoder.encode rejects null ReadableDuration values with CoderException: the coder encodes durations as a VarLong of milliseconds and has no null representation. Null durations must be handled upstream or with a nullable wrapper coder.

Source

Thrown at sdks/java/core/src/main/java/org/apache/beam/sdk/coders/DurationCoder.java:60

      new TypeDescriptor<ReadableDuration>() {};

  private static final VarLongCoder LONG_CODER = VarLongCoder.of();

  private DurationCoder() {}

  private Long toLong(ReadableDuration value) {
    return value.getMillis();
  }

  private ReadableDuration fromLong(Long decoded) {
    return Duration.millis(decoded);
  }

  @Override
  public void encode(ReadableDuration value, OutputStream outStream)
      throws CoderException, IOException {
    if (value == null) {
      throw new CoderException("cannot encode a null ReadableDuration");
    }
    LONG_CODER.encode(toLong(value), outStream);
  }

  @Override
  public ReadableDuration decode(InputStream inStream) throws CoderException, IOException {
    return fromLong(LONG_CODER.decode(inStream));
  }

  @Override
  public void verifyDeterministic() {
    LONG_CODER.verifyDeterministic();
  }

  /**
   * {@inheritDoc}
   *
   * @return {@code true}. This coder is injective.

View on GitHub (pinned to 12126d8942)

Solutions

  1. Filter or default null durations before the encoding point (e.g. Duration.ZERO or skip).
  2. Wrap with NullableCoder.of(DurationCoder.of()) for nullable duration collections.
  3. Fix the producing transform to return a non-null duration (e.g. use Duration.millis(0) as sentinel).
  4. Change the element type to Optional<Duration> with a supporting coder.

Example fix

// before
.apply(MapElements.into(TypeDescriptor.of(ReadableDuration.class)).via(e -> e.getEnd() != null ? new Duration(e.getStart(), e.getEnd()) : null))

// after
.apply(Filter.by(e -> e.getEnd() != null))
.apply(MapElements.into(TypeDescriptor.of(ReadableDuration.class)).via(e -> new Duration(e.getStart(), e.getEnd())))
Defensive patterns

Strategy: type-guard

Validate before calling

if (duration == null) { duration = Duration.ZERO; /* or skip record */ }

Type guard

boolean isEncodable(ReadableDuration d) { return d != null; }

Try / catch

try {
  pipeline.run().waitUntilFinish();
} catch (RuntimeException e) {
  if (e.getMessage() != null && e.getMessage().contains("null ReadableDuration")) {
    // add null filtering or NullableCoder.of(DurationCoder.of())
  }
  throw e;
}

Prevention

When it happens

Trigger: Encoding a PCollection containing a null org.joda.time.ReadableDuration (e.g. from a MapElements that returns null duration), when the resolved coder is DurationCoder.

Common situations: Computing durations between events where one side is missing; mapping Option/nullable fields directly to Duration; Kafka/parse pipelines producing null gaps.

Related errors


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