Executing Statements
Executing Statements
Once you hold a live Connection, the next step is to send SQL to the database. JDBC gives you the Statement interface for that job and two primary execution methods: executeQuery for reads and executeUpdate for writes. Understanding the difference — and knowing when each applies — is the foundation of every database interaction you will write.
Creating a Statement
A Statement is created from the connection object. It is also a resource that must be closed after use, so use try-with-resources:
Statement instance across threads. Each thread should create its own from its own Connection.
executeQuery — Reading Data
executeQuery(String sql) is for SQL statements that return a result set — almost always a SELECT. It returns a ResultSet that you iterate over to read rows. The method throws SQLException if the SQL is malformed or the database rejects it, and it also throws if you accidentally pass a DML statement (an UPDATE, for instance) instead of a query.
Key points about executeQuery:
- Returns a
ResultSet— nevernull, but may be empty (zero rows). - The
ResultSetcursor starts before the first row; callrs.next()to advance. - The
ResultSetis also a resource — wrap it in try-with-resources or close it explicitly. - Closing the
Statementcloses the associatedResultSetautomatically.
executeUpdate — Writing Data
executeUpdate(String sql) is for any statement that modifies data or structure: INSERT, UPDATE, DELETE, CREATE TABLE, DROP TABLE, and so on. It returns an int — the update count, meaning the number of rows affected. For DDL statements (like CREATE) the return value is always 0.
0, your WHERE clause matched nothing. Ignoring the return value silently swallows that logic error.
The execute Method — the Universal Fallback
There is a third method, execute(String sql), that handles any SQL statement regardless of whether it returns rows. It returns a boolean: true means the first result is a ResultSet, false means it is an update count. You then call stmt.getResultSet() or stmt.getUpdateCount() accordingly.
In practice execute is used when you are running dynamic or user-supplied SQL whose type you do not know at compile time — for example, a database admin tool that accepts arbitrary queries. For regular application code, prefer executeQuery or executeUpdate because the intent is explicit and the compiler will catch you if you use the wrong one.
Why Not Mix Them?
Calling executeQuery with a non-SELECT statement, or executeUpdate with a SELECT, results in a SQLException at runtime (the exact behaviour is driver-specific, but relying on it is wrong). The distinction also communicates intent to every developer who reads your code: this branch reads, that branch writes. It pairs well with connection pooling and read-replica routing, where reads can go to a replica and writes must go to the primary.
PreparedStatement, which prevents SQL injection and should be used for any query that involves external data.
Retrieving Auto-Generated Keys
When you INSERT into a table with an auto-increment primary key you often need the generated ID immediately. Pass the flag Statement.RETURN_GENERATED_KEYS to executeUpdate, then read the key from a special ResultSet:
Summary
executeQuery— SELECT statements; returns aResultSet.executeUpdate— INSERT / UPDATE / DELETE / DDL; returns a row count.execute— universal fallback when the SQL type is not known at compile time.- Always close
StatementandResultSetwith try-with-resources. - Use
RETURN_GENERATED_KEYSto retrieve auto-increment IDs after an INSERT. - Move to
PreparedStatementthe moment user data enters the SQL — never concatenate.