How does Java prevent SQL injection, and why does string concatenation of SQL fail to do so?
answer
- Concatenation = input parsed as code
- ? placeholder / :named param = data, not code
- DB compiles template before seeing the value
- Identifiers can't be bound -> allow-list
- Plan caching is a free bonus
basics
~20 sNever build SQL by gluing user input into the query text. Use PreparedStatement with ? placeholders (or JPA named parameters) and bind values separately, so input is treated as data, not as code the database runs.
solid answer
~40 sSQL injection happens when attacker-controlled text is concatenated into a SQL string, so the database parses parts of that input as SQL syntax (e.g. "' OR '1'='1"). The fix is parameterized queries: a PreparedStatement sends the query template with ? placeholders to the database first, the database compiles that fixed structure, and then you bind values via setString/setLong. Bound values are transported out-of-band as pure data and can never change the parsed query shape, regardless of quotes or keywords inside them. In JPA/Hibernate you use named parameters (:name with setParameter) for the same effect; never the string-concatenation form of createQuery. Beware false safety: PreparedStatement does not parameterize identifiers (table/column names) or LIMIT in some drivers, so those must be validated against an allow-list. Parameterization also lets the DB cache the execution plan.
code
java · 15 lines// VULNERABLE: input becomes part of the parsed SQL
String bad = "SELECT * FROM users WHERE name = '" + name + "'";
stmt.executeQuery(bad);
// SAFE: structure compiled first, value bound as data
String sql = "SELECT * FROM users WHERE name = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, name);
try (ResultSet rs = ps.executeQuery()) { /* ... */ }
}
// SAFE (JPA named parameter)
em.createQuery("SELECT u FROM User u WHERE u.name = :name", User.class)
.setParameter("name", name)
.getResultList();go deeper
States the rule: never concatenate user input into SQL; use PreparedStatement / JPA named parameters.
Explains the data-vs-code mechanism (template compiled before values are bound) and that ORMs are safe only when not string-concatenating JPQL.
Knows the limits: identifiers/sort can't be bound (allow-list them), LIKE wildcards, manual escaping is wrong, plus the plan-cache benefit.
Frames it as a trust-boundary discipline enforced systemically (static analysis, banning concatenated query APIs, least-privilege DB accounts) rather than per-call vigilance.
## The vulnerability **SQL injection (SQLi)** is when an attacker manipulates the SQL a program sends to its database by smuggling SQL syntax inside what was supposed to be plain data (a username, a search term). It is one of the oldest and most damaging web vulnerabilities — it can read, modify, or delete any data the app's DB account can touch. **Why it happens.** A database receives SQL as *text* and parses it into a query plan. If you build that text by string concatenation, the database cannot tell which characters came from the developer and which came from the user — it just parses the whole string. Example of the broken pattern: ```java String sql = "SELECT * FROM users WHERE name = '" + name + "'"; ``` If `name` is `x' OR '1'='1`, the executed SQL becomes `... WHERE name = 'x' OR '1'='1'`, which is always true and returns every row. If `name` is `x'; DROP TABLE users; --`, the input has injected an entirely new statement. The single quote in the input *broke out* of the string literal — this is the essence of an **injection**: data crossing a **trust boundary** (untrusted outside → trusted query) gets reinterpreted as code. ## The fix: parameterized (prepared) queries A **PreparedStatement** separates the query's *structure* from its *values*: ```java String sql = "SELECT * FROM users WHERE name = ?"; try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, name); // value bound separately try (ResultSet rs = ps.executeQuery()) { /* ... */ } } ``` The `?` is a **placeholder** (a *bind parameter*). The driver/database compiles the template `WHERE name = ?` *before* it ever sees `name`'s value. The value is then sent over the wire as a typed parameter, not spliced into the SQL text. Because the parsed query shape is already fixed, no quote, semicolon, or keyword inside the value can alter it — the value `x' OR '1'='1` is treated as a literal name to look up, and simply matches nothing. This is **data-vs-code separation**: the defense works at the protocol level, not by trying to clean the input. **JPA / Hibernate** give the same guarantee with **named parameters**: ```java List<User> users = em.createQuery( "SELECT u FROM User u WHERE u.name = :name", User.class) .setParameter("name", name) .getResultList(); ``` Spring Data derived queries (`findByName`) and `NamedParameterJdbcTemplate` are parameterized under the hood. The thing to *never* do is build the JPQL/SQL by concatenation. ## Important limits (so you don't get a false sense of safety) - **Identifiers are not parameterizable.** You cannot bind a table name, column name, or sort direction with `?`. If those are dynamic, validate them against a fixed **allow-list** of permitted values; never concatenate user text there. - **`LIKE` wildcards** (`%`, `_`) inside a bound value are still wildcards; escape them if the user shouldn't control matching breadth — but this is a correctness/abuse issue, not classic SQLi. - **Manual escaping is not a substitute.** Trying to escape quotes yourself is fragile (encodings, multibyte tricks) and is the wrong mental model. Parameterize. ## Bonus Parameterized queries also let the database **cache and reuse the execution plan** across calls with different values, which is a performance win, and they avoid locale/format bugs from converting values to strings yourself.
- You need a dynamic column to sort by from the UI. PreparedStatement can't bind it. What do you do?Validate the requested column and direction against a fixed allow-list of known-safe identifiers and map to a literal in code; never concatenate the raw user value into the SQL.
- Is a stored procedure automatically safe from SQL injection?No. A stored procedure that builds and EXECUTEs dynamic SQL by concatenating its parameters is just as injectable. Safety comes from parameterized execution, wherever the SQL is assembled.
saying these in an interview costs you the question
- Claiming manual quote-escaping or input sanitizing is an equivalent fix
- Thinking PreparedStatement can parameterize table/column names or ORDER BY direction
- Believing an ORM is automatically safe even with string-concatenated JPQL
- Confusing 'prepared' (plan caching) with the security property — security comes from binding, not preparing