{"record":{"id":"558933a3695a935b","repo":"tursodatabase/turso","slug":"addbatch-string-cannot-be-called-on-a-preparedsta","errorCode":null,"errorMessage":"addBatch(String) cannot be called on a PreparedStatement","messagePattern":"addBatch\\(String\\) cannot be called on a PreparedStatement","errorType":"exception","errorClass":"SQLException","httpStatus":null,"severity":"error","filePath":"bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java","lineNumber":396,"sourceCode":"  }\n\n  /** Takes the given set of parameters and binds it to the underlying statement. */\n  private void bindParams(Object[] params) throws SQLException {\n    requireNonNull(statement);\n    for (int paramIndex = 1; paramIndex <= params.length; paramIndex++) {\n      statement.bindObject(paramIndex, params[paramIndex - 1]);\n    }\n  }\n\n  @Override\n  public void addBatch() {\n    batchQueryParams.add(currentBatchParams);\n    currentBatchParams = new Object[paramCount];\n  }\n\n  @Override\n  public void addBatch(String sql) throws SQLException {\n    throw new SQLException(\"addBatch(String) cannot be called on a PreparedStatement\");\n  }\n\n  @Override\n  public void setCharacterStream(int parameterIndex, @Nullable Reader reader, int length)\n      throws SQLException {\n    requireNonNull(this.statement);\n    if (reader == null) {\n      setParam(parameterIndex, null);\n      return;\n    }\n    if (length < 0) {\n      throw new SQLException(\"setCharacterStream length must be non-negative\");\n    }\n    if (length == 0) {\n      setParam(parameterIndex, \"\");\n      return;\n    }\n    try {","sourceCodeStart":378,"sourceCodeEnd":414,"githubUrl":"https://github.com/tursodatabase/turso/blob/bad083fafbefdeae9a42ec19bdaaad8918dcf411/bindings/java/src/main/java/tech/turso/jdbc4/JDBC4PreparedStatement.java#L378-L414","documentation":"JDBC4PreparedStatement overrides addBatch(String sql) to always throw, matching the JDBC contract: a PreparedStatement is compiled once from a fixed SQL string, so adding raw SQL batches to it is invalid. The supported path is the no-arg addBatch(), which queues the currently bound parameter row (currentBatchParams) for executeBatch().","triggerScenarios":"PreparedStatement ps = conn.prepareStatement(sql); ... ps.addBatch(sql); — typically a batch helper originally written against Statement and later handed a PreparedStatement, or ORM code that mixes the two APIs.","commonSituations":"Utility methods like runBatch(Statement, List<String>) that receive a PreparedStatement; code migrated from createStatement() usage; generic batch runners keyed by SQL string.","solutions":["On a PreparedStatement, set the parameters and call the no-arg addBatch(); the SQL is fixed at prepare time.","Use conn.createStatement().addBatch(sql) when you genuinely need to batch heterogeneous SQL strings.","In shared helpers, branch on instanceof PreparedStatement so each type uses its own addBatch form."],"exampleFix":"// before\nPreparedStatement ps = conn.prepareStatement(\"INSERT INTO t VALUES (?)\");\nps.setInt(1, 42);\nps.addBatch(\"INSERT INTO t VALUES (42)\"); // throws\n\n// after\nps.setInt(1, 42);\nps.addBatch(); // queues the current parameter row","handlingStrategy":"validation","validationCode":"if (stmt instanceof PreparedStatement) {\n    ((PreparedStatement) stmt).addBatch(); // parameter batch\n} else {\n    stmt.addBatch(sql); // SQL batch\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Type batch helpers as PreparedStatement or Statement explicitly — never the base type when both may flow through.","Remember: Statement batches SQL strings; PreparedStatement batches parameter rows.","Code-review for addBatch(<string>) on any prepared object."],"tags":["jdbc","batch","prepared-statement","api-misuse"],"backgroundTag":"jdbc-api-misuse","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}