On this page
The Real Bug Was Not YAML
“Hibernate slow query logging works in hibernate.properties, but not in YAML” is an imprecise diagnosis. The useful question is narrower:
Did the Hibernate slow-query threshold enter Hibernate’s provider settings, or did it only exist somewhere in Spring’s external configuration?
Those are different states. A value can appear in application.yml and still not be forwarded to the JPA provider. A value can also be overridden by another config file before Hibernate ever sees it.
The runnable lab in labs/hibernate-slow-query-lab/ compares Spring Boot 2.7.18 with Hibernate 5.6.15.Final and Spring Boot 3.3.6 with Hibernate 6.5.3.Final, using PostgreSQL 16 in Docker and a deterministic slow SQL probe:
select 1;
select 1 from pg_sleep(0.02);
The threshold is 1 ms in the positive cases. The slow query sleeps for about 20 ms, so a correctly delivered threshold should emit an org.hibernate.SQL_SLOW log.
Short Answer
For Spring Boot JPA applications, put Hibernate-native settings under spring.jpa.properties.
For Hibernate 5.6:
spring:
jpa:
properties:
hibernate:
session:
events:
log:
LOG_QUERIES_SLOWER_THAN_MS: 1
Or:
spring.jpa.properties.hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS=1
For Hibernate 6.5, this shorter provider key also worked in the lab:
spring.jpa.properties.hibernate.log_slow_query=1
Do not put the provider key at the top level and expect Spring Boot JPA auto-configuration to pass it to Hibernate:
hibernate:
session:
events:
log:
LOG_QUERIES_SLOWER_THAN_MS: 1
In this lab, that top-level form produced no slow-query log in both Boot 2/Hibernate 5 and Boot 3/Hibernate 6.
Why This Is Easy To Misconfigure
There are three independent layers that are easy to collapse into one mental model:
| Layer | What it owns | Failure mode |
|---|---|---|
| Spring external configuration | Loads application.yml, application.properties, environment variables, command-line args, and profile files | The value is overridden or loaded in a different order than expected |
| Spring Boot JPA auto-configuration | Builds the JPA EntityManagerFactory and provider property map | The value exists in Spring config but is not forwarded to Hibernate |
| Hibernate | Interprets provider-native settings and emits SQL logs | The key name is wrong for the Hibernate version or logger level is too low |
Spring Boot’s externalized configuration documentation describes the property-source order and explicitly recommends using one file format for the application. It also says that when .properties and YAML config files are in the same location, .properties wins.
Spring Boot’s application properties appendix lists spring.jpa.properties.* as the namespace for additional native JPA provider properties. That is the bridge this post is about. Hibernate’s own key is not the full Spring Boot key; Spring Boot’s spring.jpa.properties. prefix is how the native Hibernate key gets placed into the provider map.
Lab Design
The lab uses Docker Compose rather than an in-memory database:
services:
postgres:
image: postgres:16-alpine
ports:
- "55433:5432"
environment:
POSTGRES_DB: hibernate_slow_query
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
gradle:
image: gradle:8.10.2-jdk17
depends_on:
postgres:
condition: service_healthy
I used PostgreSQL because pg_sleep(0.02) creates an actual slow database statement. The test is not measuring controller latency, Java sleeps, or result processing time. It is testing whether Hibernate’s JDBC statement logger sees a query execution that crosses the configured threshold.
The base Spring Boot test configuration intentionally keeps Hibernate statistics disabled:
spring:
jpa:
properties:
hibernate:
format_sql: false
generate_statistics: false
logging:
level:
org.hibernate.SQL: off
org.hibernate.SQL_SLOW: info
That matters because slow-query logging should not require turning on every Hibernate statistic. The lab listens to org.hibernate.SQL_SLOW, not to the broad org.hibernate logger.
Run the lab:
cd labs/hibernate-slow-query-lab
./scripts/run-matrix.sh
The successful rerun used ID 20260710T024828Z. Its generated matrix is saved at /resources/blog/post-66-hibernate-slow-query-logging-yaml-properties/matrix-summary.md, and a short log excerpt is saved at /resources/blog/post-66-hibernate-slow-query-logging-yaml-properties/slow-query-lines.txt.
How The Test Avoids False Positives
My first version used Spring Boot’s output-capture test extension and searched the entire captured console output for SlowQuery. That was too loose. A multi-context Gradle test process can include earlier log lines, and Hibernate 5 and Hibernate 6 do not use exactly the same slow-query message text.
The final test helper attaches a short-lived Logback appender directly around the probe queries:
static boolean observesSlowQuery(EntityManager entityManager) {
Logger slowQueryLogger = (Logger) LoggerFactory.getLogger("org.hibernate.SQL_SLOW");
ListAppender<ILoggingEvent> appender = new ListAppender<>();
appender.start();
slowQueryLogger.addAppender(appender);
try {
entityManager.createNativeQuery("select 1").getSingleResult();
entityManager.createNativeQuery("select 1 from pg_sleep(0.02)").getSingleResult();
} finally {
slowQueryLogger.detachAppender(appender);
appender.stop();
}
return appender.list.stream()
.map(ILoggingEvent::getFormattedMessage)
.anyMatch(message -> message.contains("SlowQuery") || message.contains("Slow query"));
}
That does two useful things:
- It checks only log events produced during the probe queries.
- It accepts both Hibernate 5’s
SlowQuery:format and Hibernate 6’sSlow query took ...format.
Observed Results
The clean run produced this matrix:
| Boot | Hibernate | Config case | Format | Property path | Observed log |
|---|---|---|---|---|---|
| 2.7.18 | 5.6.15.Final | spring jpa properties | properties | spring.jpa.properties.hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS | true |
| 2.7.18 | 5.6.15.Final | spring jpa properties | yaml | spring.jpa.properties.hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS | true |
| 2.7.18 | 5.6.15.Final | properties overrides yaml | yaml+properties | spring.jpa.properties.hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS | false |
| 2.7.18 | 5.6.15.Final | top-level hibernate property | yaml | hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS | false |
| 3.3.6 | 6.5.3.Final | spring jpa properties | properties | spring.jpa.properties.hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS | true |
| 3.3.6 | 6.5.3.Final | spring jpa properties | yaml | spring.jpa.properties.hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS | true |
| 3.3.6 | 6.5.3.Final | hibernate 6 short alias | properties | spring.jpa.properties.hibernate.log_slow_query | true |
| 3.3.6 | 6.5.3.Final | properties overrides yaml | yaml+properties | spring.jpa.properties.hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS | false |
| 3.3.6 | 6.5.3.Final | top-level hibernate property | yaml | hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS | false |
The positive cases produced the expected logger output:
Hibernate 5.6.15.Final:
SlowQuery: 21 milliseconds. SQL: '... select 1 from pg_sleep(0.02)'
Hibernate 6.5.3.Final:
Slow query took 23 milliseconds [select 1 from pg_sleep(0.02)]
This is the important conclusion: YAML was not the problem. YAML worked when the property was nested under spring.jpa.properties. The top-level hibernate.* key was the problem because Spring Boot did not forward that value as a JPA provider property in this setup.
Why .properties Changed The Result
The precedence case deliberately puts two contradictory thresholds in the same profile:
spring:
jpa:
properties:
hibernate:
session:
events:
log:
LOG_QUERIES_SLOWER_THAN_MS: 1
spring.jpa.properties.hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS=100000
The 20 ms pg_sleep() query should log if the threshold is 1 ms. It should not log if the threshold is 100,000 ms. The matrix observed false, which means the .properties value won in that same-location mixed-format scenario.
That matches Spring Boot’s documented guidance: do not mix YAML and .properties for the same configuration surface unless you are intentionally relying on precedence. For operational config, “the value exists somewhere” is not enough. The winning value is what matters.
Hibernate 5 vs Hibernate 6 Key Names
Hibernate 5.6 exposes the slow-query setting as:
hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS
The Hibernate 5.6 constant values page lists that string under LOG_SLOW_QUERY.
Hibernate 6.5’s JdbcSettings.LOG_SLOW_QUERY documents the shorter key:
hibernate.log_slow_query
The lab also observed that the longer key still worked with Spring Boot 3.3.6 and Hibernate 6.5.3.Final when it was passed through spring.jpa.properties. I would not build future production configuration around that observation alone. For Hibernate 6, prefer the documented Hibernate 6 key unless you have a compatibility reason to keep the older-looking one.
The configuration path is therefore explicit: Spring loads the winning external value, Spring Boot copies spring.jpa.properties.* into the provider map, and Hibernate interprets the native key and emits the log.
Production Checklist
When slow-query logging does not appear, debug it in this order:
-
Confirm the provider path.
The key should be under
spring.jpa.properties. If the native Hibernate key starts at the top level, Spring Boot may load it as environment config but still not put it into Hibernate’s provider settings. -
Confirm the active profile and winning file.
Check whether
application-prod.propertiesoverridesapplication-prod.yml, whether command-line args override both, and whether an environment variable is setting a different threshold. -
Confirm the Hibernate version.
Hibernate 5.6 and Hibernate 6.5 have different documented key names and different log message shapes.
-
Confirm the logger.
For this signal, enable
org.hibernate.SQL_SLOW. Turning on broad SQL logging is noisier and does not prove the threshold is working. -
Confirm the query is actually slow at the JDBC statement boundary.
The lab uses
pg_sleep(0.02)because it forces database-side execution time. A slow API response can be caused by connection acquisition, transaction waits, serialization, remote calls, or application code that Hibernate’s slow-query logger will not see.
What I Would Put In A Real Service
For a Spring Boot 3 and Hibernate 6 service, I would start with:
spring:
jpa:
properties:
hibernate:
log_slow_query: 200
logging:
level:
org.hibernate.SQL_SLOW: info
The threshold is workload-dependent. 200 ms might be too noisy for an OLAP-heavy internal tool and too relaxed for a low-latency transactional endpoint. I would set it after looking at request SLOs, database latency percentiles, and log volume.
For Spring Boot 2 and Hibernate 5:
spring:
jpa:
properties:
hibernate:
session:
events:
log:
LOG_QUERIES_SLOWER_THAN_MS: 200
logging:
level:
org.hibernate.SQL_SLOW: info
I would also keep one format per environment file. If the project uses YAML, keep the JPA/Hibernate config in YAML. If it uses .properties, keep it there. Mixing both can be useful for a test matrix, but it is a bad default for production readability.
Sources And Reproduction
- Lab code:
labs/hibernate-slow-query-lab/ - Generated matrix:
/resources/blog/post-66-hibernate-slow-query-logging-yaml-properties/matrix-summary.md - Slow-query log excerpt:
/resources/blog/post-66-hibernate-slow-query-logging-yaml-properties/slow-query-lines.txt - Spring Boot external configuration: Externalized Configuration
- Spring Boot provider properties: Common Application Properties
- Hibernate 5.6 constant values: LOG_SLOW_QUERY
- Hibernate 6.5 JDBC settings: JdbcSettings.LOG_SLOW_QUERY