signalapp/Signal-Server · error · IllegalArgumentException
redemption window too large
Error message
redemption window too large
What it means
RedemptionRange.inclusive() enforces a maximum window width: end minus start must not exceed MAX_REDEMPTION_DURATION, regardless of where the range sits relative to today. The error indicates the requested span is too wide.
Solutions
- Shrink the requested window to at most MAX_REDEMPTION_DURATION and paginate/iterate over multiple ranges.
- Cap the end: redemptionEnd = min(requestedEnd, redemptionStart.plus(MAX_REDEMPTION_DURATION)).
- Read the current constant value from source to confirm the allowed span after upgrades.
Example fix
// before
RedemptionRange.inclusive(clock, start, start.plus(Duration.ofDays(30)));
// after
Instant end = start.plus(Duration.ofDays(30));
while (!start.isAfter(requestedEnd)) {
RedemptionRange.inclusive(clock, start, end.minusSeconds(1));
start = end;
end = start.plus(MAX_REDEMPTION_DURATION);
} Defensive patterns
Strategy: validation
Validate before calling
if (end.minusSeconds(0).isAfter(start.plus(MAX_REDEMPTION_DURATION))) {
end = start.plus(MAX_REDEMPTION_DURATION);
} Type guard
boolean windowFits(Instant start, Instant end) {
return !end.isAfter(start.plus(MAX_REDEMPTION_DURATION));
} Try / catch
try {
RedemptionRange.inclusive(clock, start, end);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("window too large")) {
iterateWindows(start, end, MAX_REDEMPTION_DURATION);
} else {
throw e;
}
} Prevention
- Chunk large date spans into windows of at most MAX_REDEMPTION_DURATION
- Centralize range construction in one utility that enforces all policy bounds
- Test range construction against the current policy constant after upgrades
When it happens
Trigger: Calling inclusive() with redemptionEnd > redemptionStart + MAX_REDEMPTION_DURATION, e.g. a 60-day window when the maximum is a few days.
Common situations: Export/reporting jobs requesting long history at once; clients multiplying days incorrectly; policy change reducing MAX_REDEMPTION_DURATION while callers still request old spans.
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
- end of range must be after start of range
- timestamps must be day aligned
- start of range too far in the past
- end of range too far in the future
- Blank header
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/5de2275d37047f6c.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/auth/RedemptionRange.java:72
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))
.iterator();
}
@OverrideView on GitHub (pinned to 100ab61c82)