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
- 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)
- Before calling toLocalTime, normalize epoch-micros values: LocalTime.ofNanoOfDay(micros % 86_400_000_000_000L * 1000) or use toMicroOfDay semantics
- Verify the column actually stores a time-of-day; if it is an interval/duration, handle it as Duration instead of LocalTime
- 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
- Normalize epoch-micros/nanos with modulo one day before conversion
- Configure Debezium time converters so raw micros are converted upstream
- Confirm the column is a time-of-day, not an interval/duration
- Log raw producer values when range errors occur to find the emitting source
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
- Unable to convert to LocalDate from a java.sql.Date value '
- Unable to convert to LocalTime from unexpected value '' of t
- Time values must use number of milliseconds greater than 0 a
- Unable to convert to LocalDate from unexpected value '' of t
- Unable to convert to LocalDateTime from unexpected value ''
AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10).
Data as JSON: /api/errors/5865676c8e273e7b.
Report an issue: GitHub.