Pop quiz: What is wrong with this code?
@Service
public class TokenService {
public boolean isExpired(Token token) {
// ❌ The Villain
return token.getExpiresAt().isBefore(LocalDateTime.now());
}
}It looks innocent. But LocalDateTime.now() is a hidden dependency on the server's system clock.
- Testing is a nightmare: How do you test "token expiration" without
Thread.sleep()? You can't controlnow(). - Timezones are ignored: If your server is in UTC but
LocalDateTime.now()picks up the system default (e.g., EST), your logic breaks.

The Solution: java.time.Clock
Since Java 8, we have had a built-in abstraction for time: java.time.Clock.
Instead of asking the System for the time, you ask the Clock. And because the Clock is an object, you can inject it.
Step 1: Define the Bean
In your Spring Boot configuration, define a global Clock bean. best practice is to always force UTC.
@Configuration
public class TimeConfig {
@Bean
public Clock clock() {
return Clock.systemUTC();
}
}Step 2: Inject It
Now, refactor your service to depend on the Clock.
@Service
@RequiredArgsConstructor
public class TokenService {
// ✅ The Hero
private final Clock clock;
public boolean isExpired(Token token) {
// Ask the clock for "now"
return token.getExpiresAt().isBefore(LocalDateTime.now(clock));
}
}Step 3: Testing "Time Travel"
This is where the magic happens. In your tests, you don't use the system clock. You use a Fixed Clock.
class TokenServiceTest {
@Test
void shouldFindExpiredToken() {
// 1. Freeze time at specific date
Instant fixedInstant = Instant.parse("2023-10-01T10:00:00Z");
Clock fixedClock = Clock.fixed(fixedInstant, ZoneId.of("UTC"));
TokenService service = new TokenService(fixedClock);
// 2. Create a token that expires 1 second BEFORE the fixed time
Token token = new Token();
token.setExpiresAt(LocalDateTime.ofInstant(fixedInstant.minusSeconds(1), ZoneId.of("UTC")));
// 3. Assert (Value is deterministic!)
assertThat(service.isExpired(token)).isTrue();
}
}No Thread.sleep(). No flaky tests. Just pure deterministic logic.
Production Rule: The "Always UTC" Architecture
Dealing with users in Tokyo, London, and New York? Follow this golden rule:
Servers, APIs, and Databases always speak UTC.

1. Database
Always use TIMESTAMP WITH TIME ZONE (Postgres) or store as UTC DATETIME.
In Java, map this to Instant or OffsetDateTime. Avoid LocalDateTime for storage as it lacks timezone context.
@Entity
public class Order {
// ✅ Good: unambiguous point in timeline
private Instant createdAt;
// ❌ Bad: ambiguous (Is this 10 AM Tokyo or 10 AM NY?)
private LocalDateTime deliveryDue;
}2. API Layer
Spring Boot (via Jackson) behaves differently depending on configuration. Force it to standard ISO-8601 UTC.
# application.properties
spring.jackson.time-zone=UTC
spring.jackson.serialization.write-dates-as-timestamps=falseNow your JSON will always look like "2023-10-27T10:00:00Z".
3. The Frontend
The Browser knows the user's timezone.
- Server sends:
2023-10-27T10:00:00Z - Browser JS:
new Date('2023-10-27T10:00:00Z').toString()-> Displays10:00 PM Tokyo Time.
Conversion happens at the Edge (the user's screen), never in the core logic.
Summary
- Never use
LocalDateTime.now()in business logic. - Always inject
java.time.Clock. - Use
Clock.fixed()in tests to time-travel. - Store everything as UTC (
Instant).
Time is hard. But with Clock, at least it's testable.
Happy Coding! ⏱️