apolloconfig/apollo · error · BadRequestException
Token expires must be in the future
Error message
Token expires must be in the future
What it means
Thrown by UserTokenService.resolveExpires() when the requested expiry date exists (is non-null) but is not strictly after the current time (now). If expires is null, a default is computed from portalConfig.userTokenDefaultExpireDays() and this check passes. The check uses Date.after(), so a date equal to now also triggers it. BadRequestException → HTTP 400.
Source
Thrown at apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/UserTokenService.java:393
UserInfo userInfo = userService.findByUserId(userId);
if (userInfo == null) {
throw BadRequestException.userNotExists(userId);
}
if (userInfo.getEnabled() != USER_ENABLED) {
throw new BadRequestException("User is disabled");
}
}
private Date resolveExpires(Date requestedExpires, Date now) {
Date expires = requestedExpires;
if (expires == null) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(now);
calendar.add(Calendar.DAY_OF_YEAR, portalConfig.userTokenDefaultExpireDays());
expires = calendar.getTime();
}
if (!expires.after(now)) {
throw new BadRequestException("Token expires must be in the future");
}
Calendar maxCalendar = Calendar.getInstance();
maxCalendar.setTime(now);
maxCalendar.add(Calendar.DAY_OF_YEAR, portalConfig.userTokenMaxExpireDays());
if (expires.after(maxCalendar.getTime())) {
throw new BadRequestException("Token expires exceeds max allowed days:%s",
portalConfig.userTokenMaxExpireDays());
}
return expires;
}
private int resolveRateLimit(Integer rateLimit) {
return rateLimit == null ? 0 : rateLimit;
}
private void validateOperator(String operator) {
if (StringUtils.isBlank(operator)) {View on GitHub (pinned to d95fc18d11)
Solutions
- Set expires to a date clearly in the future (e.g., now + N days).
- If you want the default expiry, leave request.setExpires() as null — the service computes now + userTokenDefaultExpireDays.
- Ensure client and server clocks are synchronized (NTP) to avoid false past-date rejections.
Example fix
// before request.setExpires(yesterday); // after Calendar cal = Calendar.getInstance(); cal.add(Calendar.DAY_OF_YEAR, 30); request.setExpires(cal.getTime());
Defensive patterns
Strategy: validation
Validate before calling
Date expires = request.getExpires();
if (expires != null && !expires.after(new Date())) {
throw new IllegalArgumentException("Expiry must be in the future");
}
userTokenService.createToken(request, operator); Type guard
static boolean isExpiryInFuture(Date expires) {
return expires == null || expires.after(new Date());
} Try / catch
try {
userTokenService.createToken(request, operator);
} catch (BadRequestException e) {
if (e.getMessage().contains("must be in the future")) {
return Response.status(400).entity("Set expiry to a future date or leave it null for default").build();
}
throw e;
} Prevention
- Leave expires as null to use the default expiry (userTokenDefaultExpireDays).
- Sync client and server clocks via NTP to avoid false past-date errors.
When it happens
Trigger: Calling createToken() with request.setExpires() set to a past date, the current instant, or null-but-overridden-after. For example, setting expires to yesterday's timestamp, or a date that has already elapsed by the time the request reaches the server.
Common situations: Client clock skew sends a timestamp that the server considers past. Stale cached request retried after the expiry date has passed. Test or migration script hardcoded a fixed past date.
Related errors
- Invalid user token status:%s
- Invalid user token operation:%s
- Token name can not be blank
- Token namespace scope can not be null
- Token expires exceeds max allowed days:%s
AI-assisted analysis of apolloconfig/apollo@d95fc18d11 (2026-08-14).
Data as JSON: /api/errors/3fd574263d79478a.
Report an issue: GitHub.