In a plain JPA/Hibernate application, how do you make Hibernate print the SQL statements it executes, and how do you see the actual parameter values instead of the ? placeholders?
answer
- show_sql = System.out, no appender
- org.hibernate.SQL at DEBUG = real logger
- format_sql pretty, use_sql_comments names the source
- values: TRACE on orm.jdbc.bind (H6) / BasicBinder (H5)
- p6spy inlines params + timing
basics
~20 sSet hibernate.show_sql=true to print to stdout, or better, set the org.hibernate.SQL logger to DEBUG so SQL goes through your logging framework. hibernate.format_sql pretty-prints it. Parameters stay as ? until you enable TRACE on Hibernate's bind-parameter logger.
solid answer
~50 sThere are two independent switches. `hibernate.show_sql=true` writes statements straight to `System.out` — no logger, no level, no appender — so it is a local-dev toy. The real mechanism is the **`org.hibernate.SQL` logger at DEBUG**: the same statements, but routed through SLF4J/Logback/Log4j2 so you can enable them per environment and ship them to a log aggregator. `hibernate.format_sql=true` pretty-prints multi-line SQL, and `hibernate.use_sql_comments=true` prefixes each statement with a comment naming the HQL or entity operation that produced it. Neither shows bound values, because Hibernate uses `PreparedStatement` — the log shows `?`. For values you raise the bind logger to TRACE: `org.hibernate.orm.jdbc.bind` in Hibernate 6+, `org.hibernate.type.descriptor.sql.BasicBinder` in Hibernate 5. That is very chatty and puts personal data in logs, so treat it as a debugging switch. If you want one copy-pasteable statement with values inlined, use a JDBC proxy such as p6spy or datasource-proxy instead.
code
properties · 6 lineshibernate.format_sql=true
hibernate.use_sql_comments=true
# logback / log4j2 categories
logger.org.hibernate.SQL=DEBUG
logger.org.hibernate.orm.jdbc.bind=TRACEgo deeper
Know both switches by name, know that the logger version is the one to prefer, and know that parameter values need a separate TRACE category.
Explain why PreparedStatement placeholders exist, name the Hibernate 6 versus 5 bind categories, and mention use_sql_comments for tracing a statement back to its source.
Frame it as an observability decision: what is safe to leave on, the privacy cost of logging bind values, and when to move to a JDBC proxy or database-side statement logging instead.
Discuss the cost model of always-on statement logging at production volume and set an organisational default: comments on, SQL off, slow statements captured, values never logged.
## Why you cannot just read the SQL in your code Hibernate does not execute SQL you wrote — it generates it. Entity state changes become INSERT/UPDATE/DELETE at flush time, and HQL/JPQL/Criteria queries become SELECTs whose joins, aliases and column lists come from the mapping. So the first diagnostic step for almost any ORM problem is: make the generated statements visible. ## Switch 1: hibernate.show_sql `hibernate.show_sql=true` is the oldest switch. It makes Hibernate print each statement directly to `System.out`, bypassing the logging framework entirely. Consequences: you cannot set a level, cannot filter it, cannot attach it to a file appender, cannot correlate it with the rest of your log because it has no timestamp, thread name or MDC context. In a container it goes to the console stream where it may be interleaved unpredictably with real log output. It is convenient in a scratch project and inappropriate anywhere else. ## Switch 2: the org.hibernate.SQL logger Hibernate logs every statement it prepares to a category named exactly `org.hibernate.SQL`, at DEBUG level. Setting that category to DEBUG in logback.xml/log4j2.xml produces the same content as `show_sql`, but as ordinary log events: timestamped, thread-labelled, filterable, and routable. This is the version you want, because you can enable it for one test class, one environment, or one short window in staging without redeploying a code change. ## Formatting helpers - `hibernate.format_sql=true` breaks the statement across lines with indentation. Very useful for wide multi-join selects, noisy for one-line inserts. - `hibernate.highlight_sql=true` (Hibernate 6) adds ANSI colour for a terminal. - `hibernate.use_sql_comments=true` emits a leading `/* ... */` comment identifying the source: the HQL string, or something like `load com.example.Order`. This is the cheapest way to answer 'which line of code produced this query?' and it also shows up in the database's own statement log, which makes it valuable well beyond development. ## Bind parameters Hibernate binds values through `PreparedStatement`, which is what keeps it safe from SQL injection and lets the database cache plans. That is why the statement text contains `?`. The values are logged separately, by the type descriptors, at TRACE: - Hibernate 6 and later: `org.hibernate.orm.jdbc.bind` (and `org.hibernate.orm.jdbc.extract` for values read back out of the result set). - Hibernate 5: `org.hibernate.type.descriptor.sql.BasicBinder` (or the broader `org.hibernate.type`). These produce one line per parameter, so a batch of a thousand inserts becomes thousands of lines. They also dump the raw values — emails, names, tokens — into your log files, which is a privacy and compliance problem. Enable them narrowly and never by default in production. ## When logging alone is not enough The SQL log tells you what was sent, not how long it took, how many times, or whether a cache satisfied the request instead. It also does not stitch statement and parameters back into one runnable line. Tools that sit between Hibernate and the driver solve that: - **p6spy** wraps the `DataSource`/driver and logs a single line per statement with values inlined and the elapsed time. - **datasource-proxy** does the same programmatically and additionally exposes query counts, which makes it the usual basis for automated 'this endpoint must issue at most N statements' assertions. Both see everything that reaches JDBC, including statements from code that does not go through Hibernate at all. ## Production posture Running `org.hibernate.SQL` at DEBUG in production is a real cost: one log event per statement, at hundreds or thousands of statements per second, with the serialization and I/O that implies, plus log-storage bills. The normal setup is: SQL logging off by default, `use_sql_comments` on (cheap, and it makes the database-side slow-query log self-explanatory), and slow statements captured either by the database or by Hibernate's own slow-query threshold. Turn DEBUG on deliberately, briefly, and preferably on one instance.
- Why does the logged statement contain ? instead of the value, and would inlining the value be an improvement?Hibernate uses JDBC PreparedStatement, so the SQL text and the parameter values travel separately; the driver never builds one concatenated string. That is what prevents SQL injection and lets the database reuse the execution plan across calls. Inlining is only a logging convenience — tools like p6spy reconstruct the readable statement for display, they do not change what is actually sent to the database.
- You enabled DEBUG on org.hibernate.SQL and see the SELECT, but the returned entity has a null field. Does the log prove Hibernate read null from the database?No. The statement log shows what was sent, not what came back. Use the result-extraction logger (org.hibernate.orm.jdbc.extract at TRACE in Hibernate 6) to see the values actually read from the ResultSet, or a JDBC proxy that captures results. A null could equally come from a mapping mistake, a second query overwriting state, or reading a different instance from the persistence context.
saying these in an interview costs you the question
- Believing show_sql and the org.hibernate.SQL logger are the same thing, or that show_sql respects log levels and appenders
- Claiming format_sql makes bind parameter values appear
- Leaving bind-parameter TRACE logging on in production, dumping user data into log files
- Assuming the absence of a statement in the log means no database access, when a cache or an already-managed entity satisfied the call
- Saying Hibernate concatenates parameter values into the SQL string it sends