Hibernate
JPA
MySQL
CreatedDate annotation
Annotation issue

@CreatedDate annotation does not work with MySQL

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When @CreatedDate seems to “not work with MySQL,” the problem is usually not MySQL itself. @CreatedDate is a Spring Data auditing feature that sets the field value in the application before the insert reaches the database. If the timestamp stays null, the usual causes are missing auditing configuration, missing entity listeners, incompatible field mapping, or conflicting database defaults.

What @CreatedDate Actually Does

@CreatedDate does not tell MySQL to generate a timestamp automatically. Instead, Spring Data populates the property during persistence when auditing is enabled.

A typical entity looks like this:

java
1import jakarta.persistence.*;
2import org.springframework.data.annotation.CreatedDate;
3import org.springframework.data.jpa.domain.support.AuditingEntityListener;
4
5import java.time.Instant;
6
7@Entity
8@EntityListeners(AuditingEntityListener.class)
9public class OrderEntity {
10
11    @Id
12    @GeneratedValue(strategy = GenerationType.IDENTITY)
13    private Long id;
14
15    @CreatedDate
16    @Column(nullable = false, updatable = false)
17    private Instant createdAt;
18
19    public Long getId() { return id; }
20    public Instant getCreatedAt() { return createdAt; }
21}

If auditing is configured correctly, createdAt gets set before Hibernate issues the insert.

Auditing Must Be Enabled

The most common missing piece is @EnableJpaAuditing.

java
1import org.springframework.context.annotation.Configuration;
2import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
3
4@Configuration
5@EnableJpaAuditing
6public class JpaConfig {
7}

Without this, @CreatedDate is just an annotation on a field. Nothing activates the auditing logic.

The Entity Listener Must Be Registered

Even with auditing enabled globally, the entity also needs the auditing listener, either directly or through a mapped superclass.

java
@EntityListeners(AuditingEntityListener.class)

If that listener is missing, the audit fields are never populated. This is one of the most common reasons people blame the database even though the issue is at the ORM layer.

Choose a Supported Date Type

Spring Data auditing works best with standard temporal types such as:

  • 'Instant'
  • 'LocalDateTime'
  • 'OffsetDateTime'
  • 'Date'

A common modern choice is Instant:

java
@CreatedDate
private Instant createdAt;

Make sure the column type in MySQL matches how your ORM maps that Java type. The important part is consistency between the Java field and the database column.

MySQL Default Timestamps Can Conflict with App Auditing

If the database column also has its own default such as DEFAULT CURRENT_TIMESTAMP, you now have two potential sources of truth:

  1. application-side auditing
  2. database-side default generation

That can be okay, but it often causes confusion during debugging. If you want Spring Data to own the value, let Spring Data own it cleanly and avoid depending on MySQL defaults for the same column.

Example column intent:

java
@Column(nullable = false, updatable = false)

Then let the application populate it through auditing.

Time Zone Issues Are Different from Null Issues

Sometimes @CreatedDate “works” but the saved time looks wrong. That is a timezone problem, not an auditing failure. The field was populated, but the value was converted or displayed differently than expected.

Practical checks include:

  • JVM timezone
  • JDBC connection timezone settings
  • MySQL server timezone
  • application choice of Instant versus local date-time types

If the field is null, focus on auditing configuration first. If the field is populated but shifted, focus on timezone alignment.

Debugging Checklist

When createdAt is null after save, verify these items in order:

  1. @EnableJpaAuditing is present
  2. @EntityListeners(AuditingEntityListener.class) is present
  3. the entity is being saved through Spring Data JPA, not bypassing the managed lifecycle
  4. the field type is supported and mapped correctly
  5. the column is not being overwritten by another persistence rule

That checklist usually resolves the problem faster than changing MySQL column definitions randomly.

Example End-to-End

Repository and save flow:

java
1import org.springframework.data.jpa.repository.JpaRepository;
2
3public interface OrderRepository extends JpaRepository<OrderEntity, Long> {
4}
java
1import org.springframework.stereotype.Service;
2import org.springframework.transaction.annotation.Transactional;
3
4@Service
5public class OrderService {
6    private final OrderRepository repo;
7
8    public OrderService(OrderRepository repo) {
9        this.repo = repo;
10    }
11
12    @Transactional
13    public OrderEntity create() {
14        return repo.save(new OrderEntity());
15    }
16}

If auditing is wired correctly, createdAt will be set on insert without any manual assignment.

Common Pitfalls

The biggest mistake is assuming @CreatedDate is a MySQL feature rather than a Spring Data auditing feature. Another is enabling auditing globally but forgetting the entity listener on the entity or base class. Developers also often mix application-generated timestamps with database defaults and then struggle to understand which side is supposed to win. Finally, timezone mismatch can distract from the real issue if the field is populated but displayed differently than expected.

Summary

  • '@CreatedDate is handled by Spring Data auditing, not by MySQL.'
  • Enable auditing with @EnableJpaAuditing.
  • Register AuditingEntityListener on the entity or mapped superclass.
  • Use a supported temporal field type and a consistent column mapping.
  • Treat null timestamps, conflicting defaults, and timezone shifts as different problems with different fixes.

Course illustration
Course illustration

All Rights Reserved.