CallableStatement in Java

Naveen AnamaneniUpdated · 6 min read

A stored procedure is a named routine that is saved inside the database and runs there. It can contain several SQL statements and logic, and it can take parameters and return values or rows. A stored function is similar but returns a single value. In JDBC, you call both with a CallableStatement, created with con.prepareCall(...). CallableStatement extends PreparedStatement, so setting input values works the same way; what it adds is output parameters. In this lesson, you will learn the call syntax, the three kinds of parameter, and how to read results.

Syntax

JDBC defines an escape syntax in braces that works with every database:

CallSyntax
A procedure{call procedure_name(?, ?)}
A function, with its return value{? = call function_name(?)}

Parameter Kinds

KindDirectionHow to use it
INYou pass a value to the proceduresetInt(i, value), setString(i, value)
OUTThe procedure passes a value backregisterOutParameter(i, sqlType) before executing, then getInt(i), getString(i) after
INOUTBothSet it and register it

Methods

MethodDescription
con.prepareCall(sql)It is used to create a CallableStatement.
setXxx(i, value)It sets an IN parameter.
registerOutParameter(i, sqlType)It declares an OUT parameter and its type from java.sql.Types.
execute()It runs the call.
executeQuery()It runs a call that returns rows.
getXxx(i)It reads an OUT parameter or a function's return value after execution.

Example: Calling a Function and a Procedure

The routines in this example are created by the program itself so it runs anywhere. H2 lets you write stored routines in Java; other databases have their own procedure languages, shown after the example. The JDBC code that calls them is the same for every database.

Callable.java▶ Try it
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.Types;
 
public class Callable {
    public static void main(String[] args) throws Exception {
        try (Connection con =
                 DriverManager.getConnection("jdbc:h2:mem:school");
             Statement st = con.createStatement()) {
            st.execute("CREATE TABLE student ("
                + "id INT PRIMARY KEY, name VARCHAR(50), marks INT)");
            st.executeUpdate("INSERT INTO student VALUES "
                + "(1, 'Navika', 91), (2, 'Srihitha', 84), (3, 'Advitha', 34)");
 
            // Two routines stored in the database. H2 writes them in Java;
            // MySQL, PostgreSQL and Oracle use their own procedure languages.
            st.execute("CREATE ALIAS grade_for AS $$ "
                + "String gradeFor(int marks) { "
                + "return marks >= 90 ? \"A\" : marks >= 35 ? \"B\" : \"F\"; "
                + "} $$");
            st.execute("CREATE ALIAS students_above AS $$ "
                + "java.sql.ResultSet above(java.sql.Connection c, int min) "
                + "throws java.sql.SQLException { "
                + "java.sql.PreparedStatement ps = c.prepareStatement("
                + "\"SELECT name, marks FROM student "
                + "WHERE marks >= ? ORDER BY marks DESC\"); "
                + "ps.setInt(1, min); return ps.executeQuery(); } $$");
 
            // 1. A function: one value comes back through the first "?".
            try (CallableStatement cs =
                     con.prepareCall("{? = call grade_for(?)}")) {
                cs.registerOutParameter(1, Types.VARCHAR);
                for (int marks : new int[] {91, 60, 20}) {
                    cs.setInt(2, marks);
                    cs.execute();
                    System.out.println(marks + " -> grade " + cs.getString(1));
                }
            }
 
            // 2. A procedure that returns rows.
            try (CallableStatement cs =
                     con.prepareCall("{call students_above(?)}")) {
                cs.setInt(1, 80);
                try (ResultSet rs = cs.executeQuery()) {
                    while (rs.next()) {
                        System.out.println(rs.getString("name") + " "
                            + rs.getInt("marks"));
                    }
                }
            }
        }
    }
}
Output
91 -> grade A
60 -> grade B
20 -> grade F
Navika 91
Srihitha 84

Explanation:

  1. {? = call grade_for(?)} calls a function. The first ? is its return value, so it is registered as an OUT parameter with registerOutParameter(1, Types.VARCHAR). The second ? is the input, set with setInt(2, marks).
  2. After execute(), cs.getString(1) reads the returned grade. The same CallableStatement is reused for three inputs.
  3. {call students_above(?)} calls a procedure that returns rows. Its result is read like any ResultSet.

The Same Routines in MySQL

On MySQL, the routines would be written in MySQL's procedure language:

MySQL
DELIMITER //
CREATE PROCEDURE grade_for(IN p_marks INT, OUT p_grade VARCHAR(1))
BEGIN
    SET p_grade = CASE WHEN p_marks >= 90 THEN 'A'
                       WHEN p_marks >= 35 THEN 'B' ELSE 'F' END;
END //
DELIMITER ;

Here the grade is an OUT parameter of a procedure, so the call is {call grade_for(?, ?)}: set the first with setInt(1, marks), register the second with registerOutParameter(2, Types.VARCHAR), execute, and read getString(2).

Statement Types Compared

PointStatementPreparedStatementCallableStatement
RunsFixed SQLSQL with ? parametersStored procedures and functions
ParametersNoneININ, OUT, INOUT
Created withcreateStatement()prepareStatement(sql)prepareCall(sql)
ExtendsStatementPreparedStatement

When to Use Stored Procedures

In favorAgainst
Several statements run in one trip to the databaseLogic is split between Java and the database
Rules enforced close to the data, for every applicationEach database has its own procedure language; hard to move
Access can be granted to a procedure without access to the tablesHarder to test, version and debug than Java code

Many teams keep business logic in Java and use procedures sparingly, for heavy data processing that benefits from running inside the database.

Common Mistakes

Forgetting registerOutParameter. Reading the value afterward fails.

Using the wrong index for a function's return value. In {? = call f(?)}, the return value is parameter 1 and the first argument is parameter 2.

Writing database-specific call syntax. {call ...} is portable; EXEC or BEGIN ... END are not.

Interview Corner

What is a CallableStatement?

CallableStatement is the JDBC interface for executing stored procedures and functions. It is created with connection.prepareCall("{call proc(?, ?)}") or "{? = call func(?)}". It extends PreparedStatement, so IN parameters are set with setXxx; OUT and INOUT parameters are declared with registerOutParameter(index, sqlType) before execution and read with getXxx(index) afterward. A procedure can also return one or more result sets.

What is the difference between Statement, PreparedStatement and CallableStatement?

Statement executes static SQL without parameters. PreparedStatement executes precompiled SQL with IN parameters, preventing SQL injection and improving repeated execution. CallableStatement executes stored procedures and functions and additionally supports OUT and INOUT parameters. Each extends the previous one.

Practice

  1. Easy: Call grade_for for the marks 35 and 34 and explain the results.
  2. Harder: Add a routine that returns the number of students at or above a given mark, and call it with {? = call ...} and Types.INTEGER.

Quick check: CallableStatement

4 questions
1Which method creates a CallableStatement?
2What must you do before executing, for an OUT parameter?
3In {? = call f(?)}, which parameter number is the return value?
4Where does a stored procedure run?

Key Takeaways

Was this tutorial helpful?

Lesson 497 of 578