Introduction
So far in this series, we’ve learned:
- Why Java introduced the new Date & Time API.
- How every
java.timeclass works. - Which Java type should be used for different business scenarios.
- How Oracle datatypes map to Java entities.
Now it’s time to put everything together.
A timestamp in an enterprise application doesn’t exist in isolation.
It travels through multiple layers:
Browser / Mobile App
│
▼
REST API (JSON)
│
▼
Spring Boot Controller
│
▼
Service Layer
│
▼
JPA / Hibernate
│
▼
Oracle Database
│
▼
Hibernate
│
▼
REST Response
│
▼
Frontend
Every layer has the potential to introduce subtle bugs if dates and times are not handled consistently.
Typical production issues include:
- Wrong timezone displayed to users.
- Data stored in local server time.
- Incorrect audit timestamps.
- Daylight Saving Time errors.
- Failed date parsing.
- JSON serialization issues.
- Oracle session timezone mismatches.
- Different behaviour between environments.
In this article, we’ll build an end-to-end solution that follows enterprise best practices and avoids these common pitfalls.
Learning Objectives
By the end of this article, you will be able to:
- Design REST APIs using Java Time classes.
- Configure Jackson correctly.
- Persist timestamps with Hibernate.
- Configure Spring Boot for UTC.
- Handle user timezones safely.
- Design DTOs and entities correctly.
- Build production-ready microservices.
Our Example Application
We’ll build a simple Order Management service.
Customer
↓
Creates Order
↓
REST API
↓
Spring Boot
↓
Oracle
↓
Returns Response
The Order contains:
- Order Date
- Delivery Date
- Created Timestamp
- Updated Timestamp
Each field has different business semantics.
Step 1 – Designing the Entity
@Entity
@Table(name = "ORDERS")
public class Order {
@Id
private Long id;
private LocalDate orderDate;
private LocalDate deliveryDate;
private Instant createdAt;
private Instant updatedAt;
}
Notice that we are not using the same type for every field.
Each field represents a different business concept.
Why Different Types?
Order Date
private LocalDate orderDate;
Represents a business date.
No time.
No timezone.
Delivery Date
private LocalDate deliveryDate;
Also a business date.
Created Timestamp
private Instant createdAt;
Represents an exact point in time.
Suitable for:
- Auditing
- Logging
- Kafka
- Event ordering
Updated Timestamp
private Instant updatedAt;
Again, an exact timestamp.
Step 2 – DTO Design
Never expose entities directly.
Instead:
public record OrderResponse(
Long id,
LocalDate orderDate,
LocalDate deliveryDate,
Instant createdAt
){}
DTOs define the public API contract and decouple persistence from external consumers.
Step 3 – JSON Representation
Jackson automatically serializes Java Time classes.
Example response:
{
"id": 101,
"orderDate": "2026-07-10",
"deliveryDate": "2026-07-15",
"createdAt": "2026-07-10T08:35:14Z"
}
Notice:
- Dates remain dates.
- Instants remain UTC timestamps.
- ISO-8601 is used consistently.
Step 4 – REST Controller
@RestController
@RequestMapping("/orders")
public class OrderController {
@GetMapping("/{id}")
public OrderResponse getOrder(
@PathVariable Long id) {
return orderService.find(id);
}
}
Nothing special is required.
Spring Boot handles serialization automatically.
Step 5 – Service Layer
@Service
public class OrderService {
private final Clock clock;
public OrderService(Clock clock) {
this.clock = clock;
}
public OrderResponse create(OrderRequest request){
Order order = new Order();
order.setOrderDate(request.orderDate());
order.setDeliveryDate(request.deliveryDate());
order.setCreatedAt(Instant.now(clock));
repository.save(order);
return mapper.toResponse(order);
}
}
Notice we never call:
Instant.now()
directly.
Injecting Clock makes the service deterministic and testable.
Step 6 – Clock Configuration
@Configuration
public class TimeConfiguration {
@Bean
Clock clock(){
return Clock.systemUTC();
}
}
Every service now uses the same clock.
Step 7 – Unit Testing
Clock fixedClock = Clock.fixed(
Instant.parse("2026-07-10T10:00:00Z"),
ZoneOffset.UTC);
Now every test always receives the same timestamp.
No flaky tests.
Step 8 – Jackson Configuration
spring:
jackson:
time-zone: UTC
Although Instant is inherently UTC, standardizing application configuration helps avoid inconsistencies when working with other temporal types and legacy APIs.
Step 9 – Hibernate Configuration
spring:
jpa:
properties:
hibernate:
jdbc:
time_zone: UTC
This helps ensure JDBC interactions use a consistent timezone strategy.
Step 10 – Oracle Storage
Recommended storage:
| Entity Field | Oracle Column |
|---|---|
LocalDate | DATE |
Instant | TIMESTAMP(6) |
This provides:
- High precision
- UTC timestamps
- Good portability
- Clear separation of business dates and audit timestamps
Step 11 – Browser Display
Suppose Oracle stores:
2026-07-10T08:30:00Z
Browser in India
↓
Displays
10-Jul-2026
02:00 PM
Browser in London
↓
Displays
10-Jul-2026
09:30 AM
The stored value remains the same.
Only the presentation changes.
Timezone Conversion Strategy
Always follow this pipeline.
User
↓
Browser Local Time
↓
Convert to UTC
↓
Store in Database
↓
Return UTC
↓
Browser converts to local time
Never permanently store user-local timestamps unless the business requirement explicitly demands it.
Spring Boot Request Flow
JSON
↓
Jackson
↓
DTO
↓
Service
↓
Entity
↓
Hibernate
↓
Oracle
Response
Oracle
↓
Hibernate
↓
Entity
↓
DTO
↓
Jackson
↓
JSON
Every layer should preserve temporal semantics.
Exception Handling
Suppose a client sends:
{
"orderDate":"32-07-2026"
}
Spring Boot returns a validation error.
Prefer standardized error responses rather than exposing parsing exceptions directly.
Validation
@NotNull
private LocalDate orderDate;
Additional business rules can ensure, for example:
- Delivery date is not before order date.
- Order date is not in the past (where applicable).
- Expiry date is after issue date.
Logging
Always log timestamps in UTC.
Example:
2026-07-10T08:35:10Z
This greatly simplifies production troubleshooting across multiple regions.
Common Mistakes
Returning LocalDateTime from Every API
Different clients may interpret it differently because it contains no timezone or offset.
Storing Local Server Time
Never rely on the server’s default timezone for persisted timestamps.
Using Different Timezones Across Services
One microservice storing UTC and another storing local server time leads to inconsistent data.
Standardize across the platform.
Calling Instant.now() Everywhere
Inject Clock instead.
This improves testability.
Enterprise Checklist
✔ Store timestamps in UTC.
✔ Use Instant for audit fields.
✔ Use LocalDate for business dates.
✔ Use ISO-8601.
✔ Configure Hibernate for UTC.
✔ Configure Jackson consistently.
✔ Inject Clock.
✔ Separate entities from DTOs.
✔ Validate temporal business rules.
✔ Let the frontend handle display timezone.
Interview Questions
Why should Clock be injected?
To make time-dependent code deterministic and easy to test.
Why shouldn’t APIs expose LocalDateTime for audit timestamps?
Because it lacks timezone information and does not represent a globally unique instant.
Why is UTC preferred for storage?
It provides a single, unambiguous timeline across servers, regions, and clients.
Should entities and DTOs use the same temporal types?
Not always. Choose the type that best represents the persistence model and the API contract. Sometimes they are identical; sometimes they differ to satisfy business or integration requirements.
Summary
Handling dates and times correctly requires more than choosing the right Java class. Enterprise applications must adopt a consistent strategy that spans REST APIs, Spring Boot, Hibernate, Oracle, and frontend clients.
A successful approach models business concepts accurately, stores timestamps in UTC, uses ISO-8601 for data exchange, injects Clock for testability, and leaves timezone conversion to the presentation layer.
Following these principles results in systems that are easier to test, simpler to maintain, and far less susceptible to timezone-related production issues.
Coming Up Next
Part 18 – UUID Best Practices: Java, Oracle, Hibernate, Microservices & Performance
We’ll explore:
UUID.randomUUID()- UUID versions
Longvs UUID primary keys- Oracle
RAW(16)vsCHAR(36)vsVARCHAR2(36) - Hibernate mappings
- Spring Boot integration
- Index performance
- Distributed ID generation
- Migration strategies for enterprise applications