PreparedStatement in Java

Naveen AnamaneniUpdated · 6 min read

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

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

MethodDescription
setInt(i, value), setLong, setDouble, setBooleanThey 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, setTimestampThey set the older java.sql.Date and Timestamp types.
setNull(i, sqlType)It sets SQL NULL; the type comes from java.sql.Types.
setBinaryStream, setCharacterStreamThey 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

Prepared.java▶ Try it
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");
            }
        }
    }
}
Output
Srihitha | 84 | 2026-06-15
Advitha | null | 2026-07-01
1 row added

Explanation:

  1. One statement, three inserts. The SQL is prepared once; only the values change between calls to executeUpdate().
  2. Dates: setDate takes the older java.sql.Date. With a modern driver, setObject(3, LocalDate.of(...)) is simpler and works directly with java.time.
  3. setNull(2, Types.INTEGER) stores SQL NULL for Advitha's marks. A primitive setter can't do this, because setInt has no "no value".
  4. The query uses a placeholder too. The result is read with getObject("marks"), which returns null for NULL, and getObject("joined", LocalDate.class).
  5. The quote test: the name contains apostrophes. With a plain Statement and 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:

SQLValid?
SELECT * FROM student WHERE marks > ?Yes
SELECT * FROM ? WHERE id = 1No: a table name
SELECT * FROM student ORDER BY ?No: it sorts by a constant, not a column
WHERE id IN (?) with a listNo: 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

  1. Easy: Write a PreparedStatement that updates a student's marks by id, and print the number of rows changed.
  2. Harder: Write a method findByNames(Connection con, List<String> names) that builds WHERE name IN (?, ?, ...) with the right number of placeholders and sets each one.

Quick check: PreparedStatement

4 questions
1What does a ? in the SQL stand for?
2What is the index of the first placeholder?
3How do you store NULL in an INT column?
4Why does a PreparedStatement prevent SQL injection?

Key Takeaways

Was this tutorial helpful?

Lesson 491 of 578