{"record":{"id":"d62fe7b8ce4e058f","repo":"tursodatabase/turso","slug":"turso-does-not-support-stored-procedures","errorCode":null,"errorMessage":"turso does not support stored procedures","messagePattern":"turso does not support stored procedures","errorType":"exception","errorClass":"SQLException","httpStatus":null,"severity":"error","filePath":"bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Connection.java","lineNumber":225,"sourceCode":"  public CallableStatement prepareCall(String sql) throws SQLException {\n    return prepareCall(\n        sql,\n        ResultSet.TYPE_FORWARD_ONLY,\n        ResultSet.CONCUR_READ_ONLY,\n        ResultSet.CLOSE_CURSORS_AT_COMMIT);\n  }\n\n  @Override\n  public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency)\n      throws SQLException {\n    return prepareCall(sql, resultSetType, resultSetConcurrency, ResultSet.CLOSE_CURSORS_AT_COMMIT);\n  }\n\n  @Override\n  public CallableStatement prepareCall(\n      String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)\n      throws SQLException {\n    throw new SQLException(\"turso does not support stored procedures\");\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(String sql) throws SQLException {\n    return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)\n      throws SQLException {\n    return prepareStatement(\n        sql, resultSetType, resultSetConcurrency, ResultSet.CLOSE_CURSORS_AT_COMMIT);\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(\n      String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)\n      throws SQLException {","sourceCodeStart":207,"sourceCodeEnd":243,"githubUrl":"https://github.com/tursodatabase/turso/blob/244cde92a7df7f9b8b8b7a4075c35a12977e303e/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Connection.java#L207-L243","documentation":"Connection.prepareCall(...) - the entry point for CallableStatement used to invoke stored procedures - throws SQLException (not SQLFeatureNotSupportedException) at JDBC4Connection.java:225, because the SQLite engine turso wraps has no stored procedures. All four prepareCall overloads funnel into this one rejection. Note the exception type: catching only SQLFeatureNotSupportedException will miss it.","triggerScenarios":"conn.prepareCall(\"{ call compute_totals(?) }\"); MyBatis mappers with statementType=\"CALLABLE\"; JPA/Hibernate native queries wrapped in {call ...} syntax; legacy DAO code written against Oracle/SQL Server/MySQL migrated to turso; ORM tooling that probes callable support.","commonSituations":"Porting an enterprise codebase that relied on stored procedures; Spring StoredProcedure or SimpleJdbcCall helpers; reporting tools that default to CallableStatement for anything resembling a procedure call; schema migrations that left procedure-call statements behind after the procedures themselves were dropped.","solutions":["Replace the stored-procedure call with plain SQL: move the logic into the application, or into SQL statements executed via prepareStatement().","For MyBatis, change statementType=\"CALLABLE\" to statementType=\"PREPARED\" with an equivalent SELECT/INSERT.","Catch SQLException (the driver throws SQLException here, not SQLFeatureNotSupportedException) if a shared layer must tolerate it.","Grep the migrated codebase for prepareCall and '{call' / '{?=' patterns and rewrite every hit."],"exampleFix":"// before\ntry (CallableStatement cs = conn.prepareCall(\"{ call get_user(?, ?) }\")) {\n    cs.setLong(1, id); cs.execute();\n}\n\n// after\ntry (PreparedStatement ps = conn.prepareStatement(\"select name from users where id = ?\")) {\n    ps.setLong(1, id);\n    try (ResultSet rs = ps.executeQuery()) { ... }\n}","handlingStrategy":"try-catch","validationCode":"// detect up front and avoid CallableStatement entirely\n// turso/SQLite has no stored procedures: route procedure-style calls to\n// plain SQL executed via prepareStatement","typeGuard":null,"tryCatchPattern":"try {\n    stmt = conn.prepareCall(sql);\n} catch (SQLException e) { // note: SQLException, NOT SQLFeatureNotSupportedException\n    if (e.getMessage().contains(\"stored procedures\")) {\n        // rewrite as plain SQL via prepareStatement\n    } else { throw e; }\n}","preventionTips":["Never call prepareCall on turso - the engine has no stored procedures.","In MyBatis use statementType=\\\"PREPARED\\\", never CALLABLE.","Grep migrations for '{call' and '{?=' escape-syntax patterns.","Remember this throws SQLException, so SQLFeatureNotSupportedException-only handlers will miss it."],"tags":["java","jdbc","turso","sqlite","stored-procedures"],"backgroundTag":"stored-procedure-not-supported","analyzedSha":"244cde92a7df7f9b8b8b7a4075c35a12977e303e","analyzedAt":"2026-08-20T07:02:18.389Z","contentChangedAt":"2026-08-20T07:02:18.389Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}