---
title: Abstracting Java System Time Creation with InstantSource to Improve Testability
summary: In this article we explain Java's InstantSource vs Clock, how to inject it for testable time handling, and DB‑based time tricks.
tags: ["Java", "Testing", "JUnit", "PostgreSQL", "Spring Boot"]
categories: ["Programming", "Java", "java", "time"]
date: 2025-12-19T06:54:14Z
updated: 2025-12-19T11:22:54Z
---

When obtaining system time (current time) in Java, it's common to use methods like `Instant.now()` or `LocalDateTime.now()`. However, these methods depend on the OS system clock, making it difficult to control system time during testing.

The JDK provides classes for abstracting system time creation:

* `java.time.Clock` - Added in JDK 8
* `java.time.InstantSource` - Added in JDK 17

The difference between `Clock` and `InstantSource` is that the former holds timezone information. `InstantSource` only handles `java.time.Instant` generation.
Also, `Clock` is an abstract class, while `InstantSource` is an interface.

When using `Clock`, you obtain date and time as follows:

```java
Clock clock = Clock.systemUTC();
Clock clock = Clock.systemDefaultZone();

Instant now = clock.instant();
OffsetDateTime now = OffsetDateTime.now(clock);
LocalDateTime now = LocalDateTime.now(clock);
LocalDate now = LocalDate.now(clock);
```

When using `InstantSource`, you obtain time as follows:

```java
InstantSource instantSource = InstantSource.system();

Instant now = instantSource.instant();
```

System time (`Instant`) and user localization (`ZoneId`) are inherently separate concerns, but Clock couples them together.
`InstantSource` was added in JDK 17 based on the idea that there should be a simple interface for obtaining only system time.
More detailed background can be found [here](https://mail.openjdk.org/pipermail/core-libs-dev/2021-May/077213.html).

The following examples use `InstantSource`, but since `Clock` implements the `InstantSource` interface, `Clock` instances can also be used as `InstantSource`.
For systems used only within Japan where the timezone is fixed, using `Clock` might be more convenient for generating `LocalDate` and similar operations.
Note that conversion from `InstantSource` to `Clock` can be done as follows:

```java
Clock clock = instantSource.withZone(ZoneId.systemDefault());
```

In actual applications, `InstantSource` is commonly injected using Dependency Injection (DI) containers. For example, when using Spring Boot, you would define a Bean as follows:

```java
import java.time.InstantSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
class AppConfig {

	@Bean
	InstantSource instantSource() {
		return InstantSource.system();
	}

}
```

Since `InstantSource` is a Functional Interface, it can also be implemented with lambda expressions:

```java
	@Bean
	InstantSource instantSource() {
		return Instant::now;
	}
```

When you want to create an `Instant` in your code, inject and use `InstantSource`:

```java
@Service
public class MessageService {
	private final InstantSource instantSource;

	public MessageService(InstantSource instantSource) {
		this.instantSource = instantSource;
	}

	public Message createMessage(String content) {
		Instant now = instantSource.instant();
		return new Message(content, now);
	}
}
```

Since `InstantSource` is an interface, it's easy to replace during testing.
Here's an example test code. This uses Mockito to mock `InstantSource` and return a specific time:

```java
import java.time.Instant;
import java.time.InstantSource;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;

@ExtendWith(SpringExtension.class)
@Import(MessageService.class)
class MessageServiceTest {

	@Autowired
	MessageService messageService;

	@MockitoBean
	InstantSource instantSource;

	@Test
	void createMessage() {
		given(instantSource.instant()).willReturn(Instant.parse("2026-01-01T00:00:00.00Z"));
		Message message = messageService.createMessage("Hello, World!");
		assertThat(message.toString()).isEqualTo("Message[content=Hello, World!, timestamp=2026-01-01T00:00:00Z]");
	}

}
```

When you want to create a `LocalDate`, do it as follows:

```java
		ZoneId zoneId = ZoneId.systemDefault(); // or ZoneId.of("Asia/Tokyo");
		LocalDate now = instantSource.instant().atZone(zoneId).toLocalDate();
```

Alternatively, it might be better to set the timezone at injection time:

```java
@Service
public class MessageService {
	private final Clock clock;

	public MessageService(InstantSource instantSource) {
		this.clock = instantSource.withZone(ZoneId.systemDefault());
	}

	public Message createMessage(String content) {
		LocalDate now = LocalDate.now(this.clock);
		return new Message(content, now);
	}
}
```

When you want to change `ZoneId` per user, you can use methods like `org.springframework.format.datetime.standard.DateTimeContextHolder` to hold `ZoneId` in thread-local storage.

A common pattern in enterprise development is obtaining system time from a database. This is effective when you want to test specific times during system testing.
Here's an example of obtaining system time from a database, assuming PostgreSQL:

Suppose you have a table for setting specific system times:

```sql
CREATE TABLE IF NOT EXISTS system_date
(
    date_time TIMESTAMP WITH TIME ZONE
);
```

Here's an example that uses the datetime from this table if data exists, or the database's current time as system time if no data exists:

```java
import java.time.InstantSource;
import java.time.OffsetDateTime;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.simple.JdbcClient;

@Configuration(proxyBeanMethods = false)
class AppConfig {

	@Bean
	InstantSource jdbcInstantSource(JdbcClient jdbcClient) {
		return () -> jdbcClient.sql("SELECT COALESCE((SELECT date_time FROM system_date), NOW())")
			.query(OffsetDateTime.class)
			.single()
			.toInstant();
	}

}
```

Alternatively, if you have a table that holds an offset (in minutes) from the current time in the database:

```sql
CREATE TABLE system_date
(
    offset_minutes INT NOT NULL
);
```

Here's an example that adds the value in minutes to the system time if data exists in this table, or adds 0 if no data exists:

```java
import java.time.InstantSource;
import java.time.OffsetDateTime;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.simple.JdbcClient;

@Configuration(proxyBeanMethods = false)
class AppConfig {

	@Bean
	InstantSource jdbcInstantSource(JdbcClient jdbcClient) {
		return () -> jdbcClient.sql("""
				SELECT
				    NOW() + MAKE_INTERVAL(
				        mins => COALESCE(MAX(offset_minutes), 0)
				    )
				FROM
				    system_date
				""").query(OffsetDateTime.class).single().toInstant();
	}

}
```

For example, if you want to test with a time one hour ahead, you can insert a record like this:

```sql
INSERT INTO system_date(offset_minutes) VALUES (60);
```

When testing is complete and you delete this record, it will return to the normal current time.

Using these approaches, you can change the system time by simply modifying the database contents without restarting the application.

---

By using `InstantSource`, you can abstract system time acquisition and freely control time during testing.
If you're directly using `Instant.now()` or `LocalDate.now()`, I recommend changing to go through `InstantSource` to write more testable code.
