apache/seatunnel · error · java.lang.IllegalArgumentException

Time values must use number of milliseconds greater than 0 a

Error message

Time values must use number of milliseconds greater than 0 and less than 86400000000000

What it means

toLocalTime converts a java.time.Duration (Debezium's io.debezium.time.MicroTime/ NanoTime path may surface as Duration) into LocalTime via ofNanoOfDay, but only if the nanos are within 0..86400000000000 (one day). Out-of-range durations mean the source produced a time value that does not fit in a single day, so the library rejects it. Note the message text says 'milliseconds' while the check is on nanos — a known wording bug.

Source

Thrown at seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/utils/TemporalConversions.java:139

            return LocalTime.of(
                    timestamp.getHours(),
                    timestamp.getMinutes(),
                    timestamp.getSeconds(),
                    timestamp.getNanos());
        }
        if (obj instanceof java.util.Date) {
            java.util.Date date = (java.util.Date) obj;
            long millis = (int) (date.getTime() % MILLISECONDS_PER_SECOND);
            int nanosOfSecond = (int) (millis * NANOSECONDS_PER_MILLISECOND);
            return LocalTime.of(
                    date.getHours(), date.getMinutes(), date.getSeconds(), nanosOfSecond);
        }
        if (obj instanceof Duration) {
            Long value = ((Duration) obj).toNanos();
            if (value >= 0 && value <= NANOSECONDS_PER_DAY) {
                return LocalTime.ofNanoOfDay(value);
            } else {
                throw new IllegalArgumentException(
                        "Time values must use number of milliseconds greater than 0 and less than 86400000000000");
            }
        }
        if (obj instanceof String) {
            // The TIMETZ column is returned as a String which we initially parse here
            // The parsed offset-time potentially has a zone-offset from the data, shift it after to
            // GMT.
            final OffsetTime offsetTime =
                    OffsetTime.parse((String) obj, TIME_WITH_TIMEZONE_FORMATTER);
            return offsetTime.toLocalTime();
        }
        throw new IllegalArgumentException(
                "Unable to convert to LocalTime from unexpected value '"
                        + obj
                        + "' of type "
                        + obj.getClass().getName());
    }

View on GitHub (pinned to cf67b549a7)

Solutions

  1. Register Debezium's time converters (converters=* or specific converter classes) so temporal columns arrive as correctly typed values (e.g. LocalTime via io.debezium.time.Converter)
  2. Before calling toLocalTime, normalize epoch-micros values: LocalTime.ofNanoOfDay(micros % 86_400_000_000_000L * 1000) or use toMicroOfDay semantics
  3. Verify the column actually stores a time-of-day; if it is an interval/duration, handle it as Duration instead of LocalTime
  4. Clamp/validate the value at the call site and log the raw value to diagnose the producer

Example fix

// before
LocalTime t = TemporalConversions.toLocalTime(duration); // throws if out of range
// after
long nanos = duration.toNanos();
if (nanos < 0 || nanos > 86_400_000_000_000L) {
    nanos = ((nanos % 86_400_000_000_000L) + 86_400_000_000_000L) % 86_400_000_000_000L;
}
LocalTime t = LocalTime.ofNanoOfDay(nanos);
Defensive patterns

Strategy: validation

Validate before calling

boolean isConvertibleDuration(Duration d) {
    long nanos = d.toNanos();
    return nanos >= 0 && nanos <= 86_400_000_000_000L;
}

Type guard

null

Try / catch

try {
    LocalTime t = TemporalConversions.toLocalTime(value);
} catch (IllegalArgumentException e) {
    LOG.warn("Time value out of day range: {}", value);
    throw new IllegalStateException("Invalid time-of-day in source data: " + value, e);
}

Prevention

When it happens

Trigger: Calling toLocalTime/localTime with a Duration whose toNanos() is negative or exceeds 86400000000000 (24h), e.g. a MicroTime value larger than a day, an epoch-based micros value passed without epoch adjustment, or negative durations from misparsed TIMETZ data.

Common situations: Debezium time.precision.mode/connect config emitting microseconds since epoch that were not converted (Debezium's MicroTimeConverter not registered); a source column storing durations or intervals rather than a time-of-day; timezone conversion shifting the value out of range.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10). Data as JSON: /api/errors/5865676c8e273e7b. Report an issue: GitHub.