PreparedStatement and SQL Injection

The Attack

Building SQL by string concatenation lets a user inject SQL. With the input ' OR '1'='1 the login query below returns every user, and the attacker logs in without a password.

// NEVER do this
String sql = "SELECT * FROM users WHERE email = '" + email + "' AND password_hash = '" + hash + "'";
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(sql);

// email = "' OR '1'='1' -- " produces:
// SELECT * FROM users WHERE email = '' OR '1'='1' -- ' AND password_hash = '...'
//                                       ▲ always true      ▲ rest is a comment

The Fix

With PreparedStatement the SQL is sent and compiled first; parameter values are sent separately and are always treated as data, never as SQL. It is also faster when the same statement runs many times.

String sql = "SELECT id, name FROM users WHERE email = ? AND password_hash = ?";
try (PreparedStatement ps = con.prepareStatement(sql)) {
    ps.setString(1, email);       // "' OR '1'='1" is just a strange email that matches nobody
    ps.setString(2, hash);
    try (ResultSet rs = ps.executeQuery()) {
        return rs.next() ? Optional.of(new User(rs.getLong("id"), rs.getString("name"))) : Optional.empty();
    }
}

Note: Placeholders work only for values. Table names, column names and ORDER BY fields cannot be parameters — validate them against a whitelist (e.g. Set.of("price", "name")).