I have the following environment:
- Spring-Boot (v3.0.6)
- Spring (v6.0.8)
- Spring-Data (v3.0.5)
- Hibernate (v6.1.7.Final)
- MySQL Connector J (v8.0.33)
I saved a LocalDate
in UTC timezone without any problems, but if I wanted to read the LocalDate
from the database it always shifted one day off (my machine TimeZone is Europe/Berlin
).
Maybe someone has the opposite problem, so that their LocalDate
is shifted one day on.
Most of the previous answers couldn't really help me. The only solution, which I did not want to use, because it was impractical, was the following start parameter -Duser.timezone=UTC
.
1. Change TimeZone.getDefault()
I tried to change the default TimeZone
via a simple @PostConstruct
method in Spring-Boot in my @SpringBootApplication
.
@PostConstruct
public void changeTimezone() {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}
Still one day off.
2. TimeZone
in MySQL Connection String
I tried to set the serverTimezone
via mysql connection string with the following parameter:
jdbc:mysql://{host}/{database}?serverTimezone=UTC
Still one day off.
3. Tell Hibernate the TimeZone
Next thing is to tell hibernate which TimeZone
I wanted to use. I've done that via hibernate properties and passed them into my LocalContainerEntityManagerFactoryBean
instead of setting them into my application.properties
.
spring.jpa.properties.hibernate.jdbc.time_zone=UTC
Surprise... Still one day off..
(Note: I also added the TimeZoneStorageType.NORMALIZE_UTC
of hibernate 6, but it shouldn't effect the LocalDate
java or java.sql.Date
type.)
spring.jpa.properties.hibernate.timezone.default_storage=NORMALIZE_UTC
(HIBERNATE 6 ONLY)
4. Create debug hibernate type and check whats going on.
I debugged through a custom created hibernate descriptor type for LocalDate
. It quickly became clear that hibernate was doing everything
right and the "error" must lie deeper. So I jumped into mysql-connector-j source code.
The implementation of java.sql.ResultSet
ResultSetImpl
in the mysql-connector-j is parsing the java.sql.Date
values with the TimeZone
of the NativeServerSession
.
See this method in ResultSetImpl
It uses the ServerSession#getDefaultTimeZone()
method to retrieve the "wanted" TimeZone
.
See the getDefaultTimeZone()
method
As you can see, it checks if it should return the "cached" TimeZone defaultTimeZone
field.
If not, it recalls the TimeZone.getDefault()
method.
Now the problem with Spring-Boot is, that the ServerSession
object is already constructed and has cached the previous TimeZone.getDefault()
(default jvm TimeZone
) in the field linked above, before you can change the TimeZone
in a @PostConstruct
method via Spring like I've tried.
To fix the issue you can just add these parameters to the mysql connection string:
serverTimezone=UTC
- Sets the TimeZone
useLegacyDatetimeCode=false
- Disables the usage of legacy datetime codes
forceConnectionTimeZoneToSession=true
- Forces the serverTimezone
parameter to the session
cacheDefaultTimezone=false
- Disables the caching TimeZone
problem with spring-boot
Example connection string:
jdbc:mysql://{host}/{database}?serverTimezone=UTC&useLegacyDatetimeCode=false&forceConnectionTimeZoneToSession=true&cacheDefaultTimezone=false
I could not find any documentation for some parameters, so I link here again to the PropertyKey
class where I found the parameters.
I hope I was able to help someone.