JWT Expiry and Token Refresh – Java Backend Interview
Fast recall
Quick revision
Core answer
exp time. A client may then use a separately protected refresh token at the authorization server to obtain a new access token, but it should not refresh blindly for every 401. Access and refresh lifetimes are chosen from risk and user experience rather than fixed numbers. For higher-risk clients, rotate refresh tokens, store each active token or family server-side, and detect reuse so a replay can revoke the affected family.Interview-ready guide
Interview answer
An access token is accepted only until its expiry time. After that, the resource server rejects it. A client can obtain another access token from the authorization server by presenting a separate refresh token, or the user can authenticate again. The refresh token is not sent to normal APIs and may be an opaque random value rather than a JWT.
The access-token and refresh-token lifetimes depend on risk, client type, data sensitivity, and acceptable sign-in frequency. Shorter access-token life reduces the replay window, while a longer refresh credential improves usability but needs stronger storage and control. There is no universal 15-minute or seven-day setting.
For clients that use refresh-token rotation, the server invalidates the presented token and creates a replacement in one atomic operation. If an old token is later reused, that can signal theft, and the server can revoke the token family and require sign-in. The client should refresh only when it knows expiry is the cause, not retry every 401 forever.
JWT Expiry and Token Refresh – Java Backend Interview
Direct Answer
The resource server rejects an access token after its exp time. A client may then use a separately protected refresh token at the authorization server to obtain a new access token, but it should not refresh blindly for every 401. Access and refresh lifetimes are chosen from risk and user experience rather than fixed numbers. For higher-risk clients, rotate refresh tokens, store each active token or family server-side, and detect reuse so a replay can revoke the affected family.
Code Example
@Transactional
public TokenPair rotate(String presentedValue) {
RefreshToken current = repository.findForUpdate(hash(presentedValue))
.orElseThrow(InvalidGrantException::new);
if (current.isExpired() || current.isRevoked()) {
repository.revokeFamily(current.familyId());
throw new InvalidGrantException();
}
current.revoke();
RefreshToken replacement = RefreshToken.nextInFamily(current.familyId());
repository.save(replacement);
return new TokenPair(issueAccessToken(current.subject()), replacement.plainValueOnce());
}
Store a one-way hash of the refresh token where possible, rotate inside one transaction, and define how reuse of an old family member is detected and handled.
Common Mistakes & Pitfalls
- Setting 7-day expiry on access tokens: Freshers avoid refresh logic by making access tokens live for days. This defeats the purpose — a stolen token is valid for a week. Access tokens should be short (15-60 min) so compromised tokens expire quickly.
- Storing refresh tokens in localStorage: Browser localStorage is vulnerable to XSS attacks. Store refresh tokens in httpOnly secure cookies or in-memory variables for SPAs. If using cookies, set SameSite=Strict to prevent CSRF.
- Not invalidating refresh tokens on logout: Freshers forget to delete refresh tokens from the database on logout. If a user logs out, their refresh token should be immediately revoked so no one can use it to mint new access tokens.
- Allowing unlimited refresh token reuse: Without rotation, a leaked refresh token can be used indefinitely until it expires. Each refresh should invalidate the old token and issue a new one.
Check your understanding
Answer these without looking back at the article.
- What is refresh token rotation and why is it important?
- How do you implement logout in a JWT + refresh token system?
- How does the client handle the silent refresh — what happens UX-wise?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.