PreparedStatement in Java
A PreparedStatement is a statement whose SQL contains placeholders, written as question marks (?), in place of the values. You create it once from a connection with con.prepareStatement(sql), set each placeholder with a setXxx method, and execute it. The SQL text and the values travel to the database separately, so a value can never be mistaken for part of the command. That makes PreparedStatement safe from SQL injection, and because the database can prepare the statement once and run it many times, it is often faster too. It is the statement type you should use for almost all SQL. In this lesson, you will learn how to set parameters of different types, reuse a statement, and handle dates and NULL.
Syntax
String sql = "INSERT INTO student (name, marks) VALUES (?, ?)";
try (PreparedStatement ps = con.prepareStatement(sql)) {
ps.setString(1, "Navika"); // first ?
ps.setInt(2, 91); // second ?
ps.executeUpdate();
}Placeholders are numbered from 1, left to right.
Methods
| Method | Description |
|---|---|
setInt(i, value), setLong, setDouble, setBoolean | They set a primitive value for placeholder i. |
setString(i, value) | It sets text. |
setBigDecimal(i, value) | It sets an exact decimal, the right type for money. |
setObject(i, value) | It sets any supported object, including LocalDate and LocalDateTime. |
setDate, setTimestamp | They set the older java.sql.Date and Timestamp types. |
setNull(i, sqlType) | It sets SQL NULL; the type comes from java.sql.Types. |
setBinaryStream, setCharacterStream | They set large binary or text data from a stream. |
executeQuery() | It runs a SELECT and returns a ResultSet. |
executeUpdate() | It runs an INSERT, UPDATE or DELETE and returns the row count. |
clearParameters() | It removes all values that were set. |
addBatch(), executeBatch() | They queue and send many sets of values at once. |
The execute methods take no arguments: the SQL was given when the statement was prepared.
Example: Insert, Reuse and Query
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.Types;
import java.time.LocalDate;
public class Prepared {
public static void main(String[] args) throws Exception {
try (Connection con =
DriverManager.getConnection("jdbc:h2:mem:school")) {
try (Statement st = con.createStatement()) {
st.execute("CREATE TABLE student ("
+ "id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50), "
+ "marks INT, joined DATE)");
}
String insert =
"INSERT INTO student (name, marks, joined) VALUES (?, ?, ?)";
try (PreparedStatement ps = con.prepareStatement(insert)) {
ps.setString(1, "Navika");
ps.setInt(2, 91);
ps.setDate(3, Date.valueOf(LocalDate.of(2026, 6, 1)));
ps.executeUpdate();
// The same statement, run again with new values.
ps.setString(1, "Srihitha");
ps.setInt(2, 84);
ps.setObject(3, LocalDate.of(2026, 6, 15));
ps.executeUpdate();
ps.setString(1, "Advitha");
ps.setNull(2, Types.INTEGER);
ps.setObject(3, LocalDate.of(2026, 7, 1));
ps.executeUpdate();
}
String query = "SELECT name, marks, joined FROM student "
+ "WHERE joined >= ? ORDER BY id";
try (PreparedStatement ps = con.prepareStatement(query)) {
ps.setObject(1, LocalDate.of(2026, 6, 10));
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getString("name") + " | "
+ rs.getObject("marks") + " | "
+ rs.getObject("joined", LocalDate.class));
}
}
}
// A name with a quote in it is just data, not SQL.
try (PreparedStatement ps = con.prepareStatement(
"INSERT INTO student (name, marks) VALUES (?, ?)")) {
ps.setString(1, "Navika 'Nav' Anamaneni");
ps.setInt(2, 80);
System.out.println(ps.executeUpdate() + " row added");
}
}
}
}Srihitha | 84 | 2026-06-15
Advitha | null | 2026-07-01
1 row addedExplanation:
- One statement, three inserts. The SQL is prepared once; only the values change between calls to
executeUpdate(). - Dates:
setDatetakes the olderjava.sql.Date. With a modern driver,setObject(3, LocalDate.of(...))is simpler and works directly withjava.time. setNull(2, Types.INTEGER)stores SQLNULLfor Advitha's marks. A primitive setter can't do this, becausesetInthas no "no value".- The query uses a placeholder too. The result is read with
getObject("marks"), which returnsnullforNULL, andgetObject("joined", LocalDate.class). - The quote test: the name contains apostrophes. With a plain
Statementand string joining, they would break the SQL. Here they are stored as ordinary characters, with no escaping needed.
Why It Can Be Faster
When the database receives SQL, it must parse it, check it, and plan how to run it. With a PreparedStatement, that work can be done once, and each later execution only sends new values. For a statement run thousands of times, the saving is real. Combined with batching (see "Batch Processing in JDBC"), it is the fastest way to insert many rows.
What a Placeholder Can't Be
A ? stands for a value only. It can't replace a table name, a column name, or a keyword such as ASC:
| SQL | Valid? |
|---|---|
SELECT * FROM student WHERE marks > ? | Yes |
SELECT * FROM ? WHERE id = 1 | No: a table name |
SELECT * FROM student ORDER BY ? | No: it sorts by a constant, not a column |
WHERE id IN (?) with a list | No: one ? is one value; write one ? per item |
When a table or column name must vary, choose it in Java from a fixed list of allowed names, never from user input directly.
Common Mistakes
Numbering placeholders from 0. They start at 1; index 0 throws SQLException.
Leaving a placeholder unset. Executing throws an error saying the parameter isn't set.
Putting quotes around a placeholder. WHERE name = '?' searches for a literal question mark. Write WHERE name = ?.
Passing SQL to executeUpdate(sql) on a PreparedStatement. The no-argument form is the one to call.
Interview Corner
What is a PreparedStatement and what are its advantages?
A PreparedStatement is a precompiled SQL statement with ? placeholders whose values are supplied through setXxx methods. Advantages: it prevents SQL injection because values are sent separately from the SQL and are never parsed as commands; it performs better for repeated execution because the database can parse and plan the statement once; it handles quoting, dates and binary data correctly through typed setters; and it supports batch execution. It is the recommended choice for nearly all SQL with parameters.
Practice
- Easy: Write a
PreparedStatementthat updates a student's marks by id, and print the number of rows changed. - Harder: Write a method
findByNames(Connection con, List<String> names)that buildsWHERE name IN (?, ?, ...)with the right number of placeholders and sets each one.
Key Takeaways
Was this tutorial helpful?