Question mark ‘?’ characters as text in JDBC

Many people wonder how insert strings containing question mark characters into their database queries via JDBC. For those unfamiliar with the problem, ? is a reserved character in JDBC queries for parameterizing inputs. For example, if you have run the same query searching for a user but each time with a different name, JDBC offers you the ability to precompile and save the parameterized form of the query with ?’s, thereby saving the overhead of creating lots of new database statements. First, let’s frame the problem. Consider the following code:

PreparedStatement ps = conn.prepareStatement("SELECT * FROM widgets WHERE note LIKE '%long?'");
ResultSet rs = ps.executeQuery();

Description: This code searches for all widgets with note field that ends in the phrase “long?”, such as “This is how long?”.

Your first thought might be why make this a PreparedStatement at all (which supports parameters), you could just as easily do it with a Statement (which does not support parameters). Under most circumstances, it is a good coding practice to use PreparedStatement over Statements, even if you don’t have any input parameters. It allows you add parameters easily such as:

PreparedStatement ps = conn.prepareStatement("SELECT * FROM widgets WHERE size > ? AND description LIKE '%long?'");
ps.setInt(1,100);
ResultSet rs = ps.executeQuery();

Question: Will this code compile and run?

The answer is that it will compile, but under most circumstances (depending on the JDBC driver) it will not run. Why? The answer highlights just how dumb JDBC drivers really are. In short, they don’t really understand anything about the data they are parsing other than “I see a question mark, let me replace it with something!”. In this example, the user replaced the first ? with an integer, but did not replace the second question mark. In this regard, the JDBC driver will throw a runtime exception prior to sending the code to the database. Also note this code will have the same problem whether you are inserting the value ‘%long?’ into the database or reading it; as I said the JDBC driver knows very little about the query you’re constructing other than its find and replace mentality.

There’s a number of solutions available although my favorite is the following:

PreparedStatement ps = conn.prepareStatement("SELECT * FROM widgets WHERE size > ? AND description LIKE ?");
ps.setInt(1,100);
ps.setString(2,"%long?");
ResultSet rs = ps.executeQuery();

Notice I don’t need the single quotes around the parameter, JDBC will do this for me. This is better not just because it solves our original problem, the code will now run, but we’ve parameterized a messy string query! Solving the problem and enforcing good code practices is a win-win. What might throw you for a loop is you’ve increased the number of question mark ?s in the code by one. Whereas before the second ? in the query was a character representing text to be searched on, the second ? now represents a parameter JDBC should replace. It could be replaced with our target string ‘%long?’ or something without a question mark at all such as ‘horse’. Part of the advantage of parameterizing your inputs in the first place is you don’t have to worry about such situations if a user enters a question mark as a value.