PreparedStatement

So far, every example has built SQL by gluing strings together. That works for fixed, hardcoded SQL, but the moment a value comes from a user — a search box, a login form, a URL parameter — string concatenation becomes dangerous. PreparedStatement is JDBC's answer: it precompiles the SQL once and lets you bind values afterward, safely.

Statement vs. PreparedStatement

With a plain Statement, you build the full SQL text yourself, including any values, and send the whole string to the database every time. With a PreparedStatement, you write the SQL once with ? placeholders standing in for values. The database parses and compiles that SQL ahead of time, and each time you run it you just supply the values for the placeholders — they are never mixed into the SQL text itself.

StatementPreparedStatement
SQL textBuilt fresh each time, values includedWritten once with ? placeholders
ValuesConcatenated into the stringBound separately with setX() methods
SafetyVulnerable to SQL injectionSafe — values are never treated as SQL
PerformanceRecompiled every runCan be precompiled once, reused many times

The SQL Injection Risk

Here is a genuinely vulnerable login check, built the way the earlier pages built SQL — by concatenating a value straight into the query string:

// DANGEROUS - do not do this
String userInput = request.getParameter("username");
String sql = "SELECT * FROM users WHERE name = '" + userInput + "'";
ResultSet rs = stmt.executeQuery(sql);

If a normal name like Amit is entered, the final SQL is harmless: SELECT * FROM users WHERE name = 'Amit'. But an attacker doesn't have to enter a name at all. If they enter ' OR '1'='1 as the "username", the concatenated string becomes:

SELECT * FROM users WHERE name = '' OR '1'='1'
          

'1'='1' is always true, so this condition matches every row in the table, not just one user. Depending on what the surrounding code does with the result, this single trick can leak every user's data or let someone log in without knowing a password. The problem is that the attacker's input was treated as part of the SQL language itself instead of as a plain data value.

Note: This isn't a theoretical example — string-concatenated SQL like this is one of the most common real-world web vulnerabilities. Never build SQL by concatenating untrusted input into the query string.

The Safe Version: PreparedStatement

The fix is to write the SQL with a ? placeholder instead of the raw value, then bind the value using a setter method. The database driver keeps the value strictly as data — it can never be interpreted as SQL syntax, no matter what characters it contains.

String sql = "SELECT * FROM users WHERE name = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, userInput); // binds safely, never parsed as SQL

ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
  System.out.println(rs.getString("name"));
}

Now, even if userInput is ' OR '1'='1, the driver looks for a row whose name column literally equals that entire odd string — it matches nothing, instead of matching every row.

setString(), setInt(), and Parameter Positions

Placeholders are filled using a setXxx(position, value) method that matches the value's type — setString() for text, setInt() for whole numbers, setDouble() for decimals, and so on. The position is 1-based, counting the ? marks left to right in the SQL.

String sql = "INSERT INTO students (name, age, course) VALUES (?, ?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);

pstmt.setString(1, "Priya");  // 1st ? - name
pstmt.setInt(2, 22);          // 2nd ? - age
pstmt.setString(3, "JDBC");  // 3rd ? - course

int rowsInserted = pstmt.executeUpdate();
System.out.println(rowsInserted + " row(s) inserted.");

Reusing One PreparedStatement in a Loop

Because a PreparedStatement's SQL is precompiled, the same object can be reused for many executions without recompiling the SQL each time. This is especially useful when inserting many rows — create the PreparedStatement once, then in each loop iteration just rebind the values and execute.

String sql = "INSERT INTO students (name, course) VALUES (?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql); // compiled once

String[] names = {"Amit", "Priya", "Rahul"};

for (String name : names) {
  pstmt.setString(1, name);
  pstmt.setString(2, "JDBC");
  pstmt.executeUpdate(); // just bind + run, no recompiling
}

pstmt.close();
Rule of thumb: If a value ever comes from outside your own hardcoded SQL — user input, a variable, a loop — use PreparedStatement. It is safer than Statement and, when reused, often faster too.
Next steps: Head to ResultSet to see how to read query results properly, including the getter methods and their gotchas.