signalapp/Signal-Server · error · IllegalArgumentException
end of range too far in the future
Error message
end of range too far in the future
What it means
RedemptionRange.inclusive() rejects ranges whose end extends beyond today plus MAX_REDEMPTION_DURATION plus one day. This prevents callers from requesting redemption windows reaching into the far future.
Solutions
- Clamp redemptionEnd to today.plus(MAX_REDEMPTION_DURATION).plus(Duration.ofDays(1)).
- Compute the end as start.plus(MAX_REDEMPTION_DURATION) capped by the future limit.
- Surface the allowed maximum to clients so they request valid windows.
Example fix
// before Instant end = start.plus(Duration.ofDays(365)); RedemptionRange.inclusive(clock, start, end); // after Instant end = start.plus(MAX_REDEMPTION_DURATION); RedemptionRange.inclusive(clock, start, end);
Defensive patterns
Strategy: validation
Validate before calling
Instant today = clock.instant().truncatedTo(ChronoUnit.DAYS);
Instant maxEnd = today.plus(Duration.ofDays(MAX_WINDOW_DAYS)).plus(Duration.ofDays(1));
if (end.isAfter(maxEnd)) { end = maxEnd; } Type guard
boolean isEndWithinPolicy(Instant end, Clock clock) {
Instant today = clock.instant().truncatedTo(ChronoUnit.DAYS);
return !end.isAfter(today.plus(MAX_REDEMPTION_DURATION).plus(Duration.ofDays(1)));
} Try / catch
try {
RedemptionRange.inclusive(clock, start, end);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("too far in the future")) {
end = clampEnd(clock);
return RedemptionRange.inclusive(clock, start, end);
}
throw e;
} Prevention
- Derive end dates from start plus a bounded duration instead of accepting arbitrary client input
- Expose the maximum allowed end date in API documentation
- Add request-level validation returning 400 before reaching the domain check
When it happens
Trigger: Calling inclusive() with redemptionEnd > truncatedToday + MAX_REDEMPTION_DURATION + 1 day, e.g. pre-scheduling a redemption range months ahead.
Common situations: Generating long-lived default windows; client sending an end date far ahead; misinterpreting the allowed maximum window length constant.
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
- start of range too far in the past
- end of range must be after start of range
- timestamps must be day aligned
- redemption window too large
- Blank header
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/5787edf2eebe049e.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/auth/RedemptionRange.java:68
throws IllegalArgumentException {
final Instant today = clock.instant().truncatedTo(ChronoUnit.DAYS);
final Instant yesterday = today.minus(Duration.ofDays(1));
if (redemptionStart.isAfter(redemptionEnd)) {
throw new IllegalArgumentException("end of range must be after start of range");
}
if (!redemptionStart.truncatedTo(ChronoUnit.DAYS).equals(redemptionStart)
|| !redemptionEnd.truncatedTo(ChronoUnit.DAYS).equals(redemptionEnd)) {
throw new IllegalArgumentException("timestamps must be day aligned");
}
if (redemptionStart.isBefore(yesterday)) {
throw new IllegalArgumentException("start of range too far in the past");
}
if (redemptionEnd.isAfter(today.plus(MAX_REDEMPTION_DURATION).plus(Duration.ofDays(1)))) {
throw new IllegalArgumentException("end of range too far in the future");
}
if (redemptionEnd.isAfter(redemptionStart.plus(MAX_REDEMPTION_DURATION))) {
throw new IllegalArgumentException("redemption window too large");
}
return new RedemptionRange(
LocalDate.ofInstant(redemptionStart, ZoneOffset.UTC),
LocalDate.ofInstant(redemptionEnd, ZoneOffset.UTC));
}
@Override
public @NotNull Iterator<Instant> iterator() {
final Instant fromInstant = from.atStartOfDay(ZoneOffset.UTC).toInstant();
final Instant endInstant = end.atStartOfDay(ZoneOffset.UTC).toInstant();
return Stream
.iterate(fromInstant, redemptionTime -> redemptionTime.plus(Duration.ofDays(1)))
.takeWhile(redemptionTime -> !redemptionTime.isAfter(endInstant))View on GitHub (pinned to 100ab61c82)