In JDBC, what does PreparedStatement do that Statement does not, and why prefer it?
answer
- Who decides the SQL text, and when
- Values arrive separately from the grammar
- Placeholders are 1-based and typed
- Fixed text can be parsed once and reused
basics
~20 sPreparedStatement fixes the SQL text up front with ? placeholders and supplies values afterwards through numbered typed setters, so input is never parsed as SQL. The same object can be re-executed with new values and the parsed form reused.
solid answer
~50 s`Statement` takes a finished SQL string at execution time, so any value you want to include has to be concatenated into that string. `PreparedStatement` takes the SQL when the object is created, with a `?` in every position where a value goes, and you bind the values afterwards with `setString(1, ...)`, `setLong(2, ...)` and friends. Two things follow. First, the statement's grammar is settled before any user data is involved — a bound value is data to the database, never SQL text, whatever characters it contains. Second, because the text is stable, the driver and the database can keep the parsed form and re-use it across executions with different parameters. The setters are also typed, so dates, decimals, booleans and nulls are converted by the driver rather than by your own string formatting. In practice you should use `PreparedStatement` for essentially every query, not only the ones with parameters.
code
java · 15 lines// Statement: the value becomes part of the SQL text
Statement st = conn.createStatement();
ResultSet bad = st.executeQuery(
"SELECT id FROM users WHERE email = '" + email + "'");
// PreparedStatement: the SQL is fixed, the value is bound
try (PreparedStatement ps = conn.prepareStatement(
"SELECT id FROM users WHERE email = ?")) {
ps.setString(1, email);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
handle(rs.getLong("id"));
}
}
}go deeper
Be able to write the parameterised version from memory: prepareStatement with ?, setters numbered from 1, then executeQuery. Knowing that concatenating input into SQL is the wrong habit is table stakes at a first screen.
Explain the mechanics: the statement text is complete before values arrive, indexes count placeholders left to right, setters are typed so the driver handles conversion, and the fixed text is what makes re-use and batching possible.
Show where the mechanism stops — identifiers and keywords cannot be bound — and how you handle dynamic sorting or IN lists without reopening the concatenation habit. Be ready to talk about statement re-use across many executions.
Own the codebase-wide position: a convention that all data access binds parameters, enforced by static analysis rather than review memory, plus a plan for the dynamic-SQL corners where a fixed set of identifiers has to be maintained deliberately.
## The three statement types A JDBC `Connection` can produce three kinds of statement object. `createStatement()` returns a `Statement`, whose `executeQuery`/`executeUpdate` methods take the complete SQL text as an argument. `prepareStatement(sql)` returns a `PreparedStatement`, which is bound to one SQL string given at creation time; its execute methods take no SQL. `prepareCall(sql)` returns a `CallableStatement`, which extends `PreparedStatement` and adds registration of OUT parameters for stored-procedure calls. The distinction that matters day to day is the first two: with `Statement` the SQL text is assembled by your code every time; with `PreparedStatement` the text is fixed and only the values change. ## Placeholders and typed setters In a `PreparedStatement` every place a value would appear is written as a `?`. After creating the statement you bind each placeholder by its **1-based** position: `ps.setString(1, email)`, `ps.setLong(2, tenantId)`, `ps.setNull(3, Types.VARCHAR)`. Indexes count `?` characters left to right through the SQL text; they have nothing to do with result columns and they do not start at zero. The setters are typed, which is more valuable than it first looks. `setTimestamp` hands the driver a `java.sql.Timestamp` and lets the driver render it in whatever form the wire protocol wants; you never write a date format string and never discover at 2 a.m. that one database wanted a different literal syntax. `setBigDecimal` avoids the precision loss of formatting a decimal into text. `setNull` expresses a genuine SQL NULL, which string building cannot do without special-casing. ## What the fixed text buys you Because the SQL grammar is complete before any value is supplied, the database parses a statement whose shape cannot be changed by the data. A bound parameter is transported as a value in its own slot; the parser never sees it as part of the statement. That is why parameter binding — not filtering or escaping the input — is the standard defence against SQL injection, and why reviewers scan JDBC code for string concatenation inside `executeQuery(...)` as a first pass. The limit of the mechanism is worth stating precisely: **a `?` can stand only for a value**. It cannot stand for a table name, a column name, a sort direction, an operator, or any other piece of SQL grammar, because those participate in parsing. Queries where callers choose a sort column therefore need the identifier chosen from a fixed set inside your code and interpolated into the text, with only the values still bound. ## Reuse and plan caching The second benefit is performance under repetition. A loop that inserts a thousand rows with `Statement` sends a thousand distinct SQL texts, each of which the database must parse and plan. The same loop with one `PreparedStatement` sends one text and a thousand parameter sets, so the parsed statement can be reused. Many drivers go further and hold a server-side prepared statement, sometimes only after the same statement has been executed a few times — PostgreSQL's driver, for example, exposes this as its `prepareThreshold` setting. Whether a real server-side prepare happens is a driver and configuration question; "prepared" in the name is a JDBC-level contract, not a promise about the wire. This reuse is also the precondition for batching: `addBatch()`/`executeBatch()` on a `PreparedStatement` accumulates parameter sets against one statement. ## Generated keys and other conveniences `prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)` followed by `getGeneratedKeys()` after `executeUpdate()` returns a `ResultSet` of the keys the database assigned — the standard way to recover an auto-increment id. `Statement` supports the same flag on its execute overloads, but with a `PreparedStatement` you get it alongside safe binding. ## Practical rules Use `PreparedStatement` by default, including for queries with no parameters at all: it costs nothing, keeps the code uniform, and means a later change that adds a parameter cannot accidentally be written as concatenation. Reserve `Statement` for one-off DDL or administrative SQL that contains no external input. Close statements — ideally with try-with-resources — because each one can hold a server-side cursor. And never build an `IN (...)` list by concatenating values: generate the right number of `?` placeholders and bind each one, or use `setArray` where the driver supports it.
- Can a ? placeholder stand for a table name or an ORDER BY column?No. A placeholder binds a value only; identifiers and keywords take part in parsing, so the driver rejects them. If callers choose a sort column, map their input to an identifier your code already knows and interpolate that fixed string, keeping every real value bound.
- Does using PreparedStatement guarantee the database precompiles the statement?No. "Prepared" is a JDBC-level contract about how you supply values. Whether a server-side prepare happens depends on the driver and its configuration — PostgreSQL's driver, for instance, switches to a server-side prepared statement only after the same statement has been executed a configurable number of times.
- How would you write an IN clause with a variable number of values?Generate exactly as many ? placeholders as you have values, build the SQL from that count, and bind each value by index. Never concatenate the list itself. Cache-friendliness suffers because each distinct size is a different statement text, so some teams round the count up to fixed buckets padded with repeats.
A Statement is retyping a whole letter for each recipient; a PreparedStatement is a printed form with blanks — the wording is fixed and only the blanks are filled in, so nothing a recipient writes in a blank can change the sentence around it.
saying these in an interview costs you the question
- Says parameter indexes start at zero like arrays
- Claims placeholders can hold table or column names
- Thinks escaping quotes in the value is equivalent
- Believes PreparedStatement always means a server-side prepare
- Uses Statement for user input because the value is numeric