Skip to content

JDBC Call

@JdbcCall defines a JDBC method that executes callable text with a CallableStatement. The generated method obtains a connection, creates the statement with Connection.prepareCall(...), registers output parameters, binds inputs, executes the call, and reads the declared outputs.

@JdbcCall is intended for portable scalar input, output, and input/output parameters. For cursors, result sets, named database types, or a database-specific multiple-result contract, use @JdbcNative.

❗️Callable statements

The @JdbcCall value must not be blank. It is passed to Connection.prepareCall(...) after the processor replaces its recognised named markers with JDBC parameter markers. The callable text, its escape syntax, and its compatibility with the selected database and JDBC driver are application-owned.

Callable text supports only scalar named markers of the form :name. Each marker must match exactly one Java method parameter, one output derived from the return type, or both.

@JdbcCall("{ CALL adjust_total(:customerId, :amount, :total) }")
@JdbcName("total")
BigDecimal adjustTotal(long customerId, BigDecimal amount);

An input and an output with the same name define one INOUT position. An input-only marker defines an IN position. An output-only marker defines an OUT position.

Unnamed ? markers and expansion markers are not supported for callable text:

  • ?
  • :param.*
  • :param.{values}
  • :param.{names}

The existing SQL tokenizer remains authoritative. Colon text that it does not recognise is passed through unchanged.

The return type declares the outputs:

  • void declares no outputs
  • a return mapped by a column converter declares one output; its name is the method-level @JdbcName
  • a record mapped from its components declares one output per record component; the component name is the marker name

Every output must occur exactly once in the callable text. For a column return, @JdbcName is required and must not be blank. Records and classes handled as single column values follow this rule as well. A component-mapped record models the completed call and is never null. Therefore @Nullable and Optional<Record> are invalid for such a record itself. Individual record components may still use @Nullable or Optional<T> to represent a SQL NULL output.

record Totals(BigDecimal subtotal, BigDecimal tax) {}
@JdbcCall("{ CALL calculate_totals(:customerId, :subtotal, :tax) }")
Totals calculateTotals(long customerId);

An output is read with the getter selected by the JDBC-to-Java column converter. For one output, converter lookup uses the method return type and an optional @JdbcConverterName on the method. For multiple outputs, lookup uses each record component type and an optional @JdbcConverterName on that component.

Each callable output must use a column converter with an SQL type that does not require a ResultSet. A record return is supported when every record component can be mapped independently by such a column converter. Converters that read directly from a ResultSet are not applicable to callable outputs. Converter discovery and names follow Converter Lookup. The SQL type and getter mapping are documented with JDBC-to-Java converters.

SQL NULL follows the selected Java output type:

  • Optional<T> receives Optional.empty()
  • @Nullable T receives null
  • a primitive output fails with NullPointerException

@JdbcQueryTimeout is the only statement configuration supported by generated callable methods. It is applied with CallableStatement.setQueryTimeout(...) before execution. Other statement configuration annotations are unused on an @JdbcCall method and produce the normal unused-annotation diagnostic.

Generated callable code is JDBC-driver-independent. It does not branch on the database product or generate database-specific JDBC code. Required driver settings belong to the application’s DataSource.

For PostgreSQL procedure escape syntax, configure the PostgreSQL JDBC driver with escapeSyntaxCallMode=callIfNoReturn. For example:

jdbc:postgresql://localhost:5432/app?escapeSyntaxCallMode=callIfNoReturn

Generated code reads declared output parameters directly after execute(). It does not call getResultSet(), getMoreResults(), or getUpdateCount(), so it never silently consumes result sets or update counts. Calls that need such results must use @JdbcNative and handle their driver-specific result contract explicitly.

H2 supports the generated input-only and scalar function-return forms used by the specification tests. Its Java aliases cannot represent the generated OUT, INOUT, or multiple output forms; those are verified against the other configured databases.

Invalid callable declarations follow the processor-wide error contract. When an error is limited to one callable method, the generated method throws CodeGenerationException when it is invoked. Generated SQLException failures are wrapped in JdbcException.