apache/beam · error · IllegalArgumentException

'max-past' must be a positive long value.

Error message

'max-past' must be a positive long value.

What it means

For TIMESTAMP fields, the optional 'fields.<name>.max-past' property controls how far back in the past random timestamps may fall. It must parse to a strictly positive long (milliseconds). Zero or negative values throw an IllegalArgumentException.

Solutions

  1. Set 'max-past' to a positive integer millisecond value (e.g. 600000 for 10 minutes).
  2. Remove the 'max-past' property entirely to use the default (Instant.now()).
  3. Convert duration strings to milliseconds before writing the property.

Example fix

// before
'{"fields":{"ts":{"max-past":0}}}'
// after
'{"fields":{"ts":{"max-past":600000}}}'
Defensive patterns

Strategy: validation

Validate before calling

JsonNode p = props.path("fields." + name + ".max-past");
if (!p.isMissingNode() && p.asLong() <= 0) {
  throw new IllegalArgumentException("max-past must be > 0");
}

Try / catch

try { buildRowFn(props); } catch (IllegalArgumentException e) { /* remove or correct max-past */ }

Prevention

When it happens

Trigger: Setting fields.<name>.max-past to 0, a negative number, or a non-numeric value that asLong coerces to <= 0, on a TIMESTAMP datagen field.

Common situations: Configuring max-past: 0 thinking it disables the option; using a negative duration intending 'in the past'; pasting a duration string like '5m' that Jackson coerces to 0.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at sdks/java/extensions/sql/src/main/java/org/apache/beam/sdk/extensions/sql/meta/provider/datagen/DataGeneratorRowFn.java:187

        return (index) -> minD + (maxD - minD) * ThreadLocalRandom.current().nextDouble();
      case TINYINT:
      case SMALLINT:
      case INTEGER:
      case BIGINT:
        long minL = properties.path("fields." + fieldName + ".min").asLong(0L);
        long maxL = properties.path("fields." + fieldName + ".max").asLong(Long.MAX_VALUE);
        return (index) -> minL + (long) (ThreadLocalRandom.current().nextDouble() * (maxL - minL));
      case DECIMAL:
        double minBd = properties.path("fields." + fieldName + ".min").asDouble(0.0);
        double maxBd = properties.path("fields." + fieldName + ".max").asDouble(1000.0);
        return (index) ->
            BigDecimal.valueOf(minBd + (maxBd - minBd) * ThreadLocalRandom.current().nextDouble());
      case TIMESTAMP:
        JsonNode maxPastNode = properties.path("fields." + fieldName + ".max-past");
        if (!maxPastNode.isMissingNode()) {
          long maxPastMs = maxPastNode.asLong();
          if (maxPastMs <= 0) {
            throw new IllegalArgumentException("'max-past' must be a positive long value.");
          }
          return (index) ->
              Instant.now()
                  .minus(
                      Duration.millis(
                          (long) (ThreadLocalRandom.current().nextDouble() * maxPastMs)));
        }
        return (index) -> Instant.now();
      default:
        throw new UnsupportedOperationException("Unsupported SQL type for datagen: " + sqlTypeName);
    }
  }
}

View on GitHub (pinned to 12126d8942)