Handling Clock and Timezones Correctly in Java
Writing
JAVA ENGINEERING
January 7, 20263 min read

Handling Clock and Timezones Correctly in Java

Why LocalDateTime.now() destroys your tests and how to use java.time.Clock to handle timezones like a pro in production applications.

javaspring-boottestingtimezonesbest-practices

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.

  1. Testing is a nightmare: How do you test "token expiration" without Thread.sleep()? You can't control now().
  2. Timezones are ignored: If your server is in UTC but LocalDateTime.now() picks up the system default (e.g., EST), your logic breaks.

Timezone Chaos

How does java.time.Clock solve the hidden time dependency?

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.

Why should your production architecture always use UTC?

Dealing with users in Tokyo, London, and New York? Follow this golden rule:

Servers, APIs, and Databases always speak UTC.

UTC Architecture Flow

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=false

Now 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() -> Displays 10:00 PM Tokyo Time.

Conversion happens at the Edge (the user's screen), never in the core logic.

Summary

  1. Never use LocalDateTime.now() in business logic.
  2. Always inject java.time.Clock.
  3. Use Clock.fixed() in tests to time-travel.
  4. Store everything as UTC (Instant).

Time is hard. But with Clock, at least it's testable.

For further reading, see the java.time.Clock Javadoc, the Java Date and Time API tutorial, and the Spring Boot Jackson datetime configuration reference.

Keep Reading

Happy Coding! ⏱️

Frequently Asked Questions

Why should I avoid LocalDateTime.now() in Java?

LocalDateTime.now() creates a hidden dependency on the server's system clock, making tests non-deterministic and timezone-dependent. Instead, inject java.time.Clock as a bean and use LocalDateTime.now(clock), which lets you freeze or control time in tests using Clock.fixed().

How do I handle timezones correctly in a Spring Boot application?

Follow the 'Always UTC' architecture: store all timestamps as UTC in the database using TIMESTAMP WITH TIME ZONE, configure Jackson to serialize dates in ISO-8601 UTC format, and let the frontend convert to the user's local timezone for display. Never convert timezones in your backend business logic.

How do I test time-dependent code in Java without Thread.sleep()?

Use Clock.fixed(instant, zone) to create a deterministic clock frozen at a specific moment. Inject this fixed clock into your service during tests, giving you full control over 'now' without any sleeping or flaky timing issues.

Last updated: April 1, 2026

Rabinarayan Patra

Rabinarayan Patra

SDE II at Amazon. Previously at ThoughtClan Technologies building systems that processed 700M+ daily transactions. I write about Java, Spring Boot, microservices, and the things I figure out along the way. More about me →

X (Twitter)LinkedIn

Stay in the loop

Get the latest articles on system design, frontend and backend development, and emerging tech trends, straight to your inbox. No spam.