-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
134: Executing Parameterized Queries with PreparedStatement
I've spent a lot of time reviewing code from developers who are just starting out with database connectivity in Java. There is one pattern that consistently makes me nervous: the habit of building SQL queries using string concatenation. It seems intuitive at first—you have a variable, you have a query, so you just glue them together. But in a production environment, this is a disaster waiting to happen.
The temptation of string concatenation
Imagine you're building a simple member directory. You want to let a user search for a profile by their username. The "naive" approach looks something like this:
String username = request.getParameter("user");
String sql = "SELECT * FROM users WHERE username = '" + username + "'";
Statement statement = connection.createStatement();
ResultSet rs = statement.executeQuery(sql);
On the surface, this works. If the user types "alice", the query becomes SELECT * FROM users WHERE username = 'alice'. Everything is fine. But here is the problem: you are trusting the user to provide data, but you're treating that data as part of the executable command. You've essentially given the user a megaphone and told the database to do whatever the megaphone says.
When your database becomes an open door
This is where SQL Injection comes in. If I'm a malicious actor and I enter ' OR '1'='1 into your search box, your Java code happily concatenates that into the string. The resulting query sent to the database becomes:
SELECT * FROM users WHERE username = '' OR '1'='1'
Since '1'='1' is always true, the database ignores the username check entirely and returns every single user in your table. In a real-world scenario, a clever attacker could use this to bypass login screens, dump your entire customer list, or even drop your tables. I can't stress this enough: never, ever trust raw input in a SQL string.
Letting the driver handle the heavy lifting
The professional way to handle this is with a PreparedStatement. Instead of building a string, you define a template for your query using placeholders—represented by question marks (?). You send this template to the database first, and then you send the data separately.
String username = request.getParameter("user");
String sql = "SELECT * FROM users WHERE username = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, username); // The 1 refers to the first '?'
ResultSet rs = pstmt.executeQuery();
Notice the difference. By using pstmt.setString(), you aren't just plugging a string into a gap. You're telling the JDBC driver, "This value is data, and only data." If a user tries to enter ' OR '1'='1 now, the database will literally look for a user whose username is the string "' OR '1'='1". It won't execute it as code, and the attack fails.
The hidden performance win
Beyond security, there is a performance trade-off here that often goes unmentioned. When you use a regular Statement, the database has to parse, compile, and optimize the query every single time you run it. If you're running the same query a thousand times with different IDs, that's a lot of wasted effort.
A PreparedStatement is "pre-compiled." The database analyzes the query template once and caches the execution plan. When you swap out the parameters, the database just plugs the new values into the existing plan. It's faster, cleaner, and fundamentally more secure. It's simply the only way I'll accept parameterized queries in a professional codebase.
📋 Practical Task
Securing the Warehouse SKU Lookup
You have been handed a legacy piece of code for a warehouse management system. The current implementation for looking up a product by its SKU is vulnerable to SQL Injection. Your task is to refactor the findProductBySku method to use a PreparedStatement instead of string concatenation.
// VULNERABLE CODE - FIX THIS
public Product findProductBySku(Connection conn, String sku) throws SQLException {
String sql = "SELECT * FROM products WHERE sku = '" + sku + "'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
if (rs.next()) {
return new Product(rs.getInt("id"), rs.getString("name"));
}
return null;
}
Requirements:
- Replace the
Statementwith aPreparedStatement. - Use a parameterized placeholder (
?) for the SKU value. - Ensure the SKU parameter is bound correctly using the appropriate
setXXXmethod. - Maintain the existing return logic.
There are no comments for now.