signalapp/Signal-Server · error · IllegalArgumentException
end of range must be after start of range
Error message
end of range must be after start of range
What it means
RedemptionRange.inclusive() validates a [start, end] instant range for receipt redemption. It throws IllegalArgumentException when redemptionStart is strictly after redemptionEnd, i.e. the caller supplied a backwards range.
Solutions
- Swap or re-order the arguments so start <= end before calling inclusive().
- Validate/normalize client-supplied dates before constructing the range.
- Use Instant.isAfter as a pre-check and return a 400 to the API caller.
Example fix
// before
RedemptionRange.inclusive(clock, endDate, startDate);
// after
if (startDate.isAfter(endDate)) {
throw new BadRequestException("start must not be after end");
}
RedemptionRange.inclusive(clock, startDate, endDate); Defensive patterns
Strategy: validation
Validate before calling
if (start.isAfter(end)) {
throw new IllegalArgumentException("start must not be after end");
} Type guard
boolean isValidRange(Instant start, Instant end) {
return start != null && end != null && !start.isAfter(end);
} Try / catch
try {
RedemptionRange range = RedemptionRange.inclusive(clock, start, end);
} catch (IllegalArgumentException e) {
return Response.status(400, e.getMessage()).build();
} Prevention
- Normalize start/end ordering as min/max before constructing ranges
- Validate date-range request parameters at the resource layer
- Use a shared range-builder helper so ordering bugs cannot recur
When it happens
Trigger: Calling RedemptionRange.inclusive(clock, start, end) with start > end (even by milliseconds; equality is allowed).
Common situations: Swapped order of query parameters when computing a range from client input; timezone arithmetic producing an end earlier than start; off-by-one day when building ranges.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- timestamps must be day aligned
- start of range too far in the past
- end of range too far in the future
- redemption window too large
- PNI identity key must not be provided if existing account…
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/7b8382b7de8d84d9.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/auth/RedemptionRange.java:55
/// - `redemptionEnd` >= `redemptionStart`
/// - `redemptionStart` and `redemptionEnd` are day-aligned
/// - `redemptionStart` is yesterday or later
/// - `redemptionEnd` is tomorrow + `MAX_REDEMPTION_DURATION` or earlier
/// - The number of days requested is less than `MAX_REDEMPTION_DURATION`
///
/// @param clock Clock to use to get current day
/// @param redemptionStart The first day included in the range
/// @param redemptionEnd The last day included in the range
/// @return A {@link RedemptionRange} that can be used to iterate each day between `redemptionStart` and
/// `redemptionEnd`
/// @throws IllegalArgumentException if the redemption bounds were not valid
public static RedemptionRange inclusive(Clock clock, Instant redemptionStart, Instant redemptionEnd)
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");
}View on GitHub (pinned to 100ab61c82)