apache/beam · error · RuntimeException

Unable to create prepared statement for type: ${type}

Error message

Unable to create prepared statement for type: ${type}

What it means

Thrown by JdbcIo's statement parameter setter when a JDBC PreparedStatement setXxx call fails with a SQLException while binding a Beam schema row field to the SQL statement. The Beam FieldType is mapped to a typed caller via JdbcUtil.getPreparedStatementSetCaller(type); if the driver rejects the value for that column/type, this wrapper is raised with the offending type in the message.

Source

Thrown at sdks/java/io/jdbc/src/main/java/org/apache/beam/sdk/io/jdbc/JdbcUtil.java:382

      throws SQLException {
    ps.setNull(i + 1, type.getVendorTypeNumber());
  }

  static class BeamRowPreparedStatementSetter implements JdbcIO.PreparedStatementSetter<Row> {
    @Override
    public void setParameters(Row row, PreparedStatement statement) {
      Schema schema = row.getSchema();
      List<Schema.Field> fieldTypes = schema.getFields();
      IntStream.range(0, fieldTypes.size())
          .forEachOrdered(
              i -> {
                Schema.FieldType type = fieldTypes.get(i).getType();
                try {
                  JdbcUtil.getPreparedStatementSetCaller(type)
                      .set(row, statement, i, SchemaUtil.FieldWithIndex.of(schema.getField(i), i));
                } catch (SQLException throwables) {
                  throwables.printStackTrace();
                  throw new RuntimeException(
                      String.format("Unable to create prepared statement for type: %s", type),
                      throwables);
                }
              });
    }
  }

  private static JdbcIO.PreparedStatementSetCaller createBytesCaller() {
    return (element, ps, i, fieldWithIndex) -> {
      byte[] value = element.getBytes(fieldWithIndex.getIndex());
      if (value != null) {
        validateLogicalTypeLength(fieldWithIndex.getField(), value.length);
      }
      ps.setBytes(i + 1, value);
    };
  }

  private static JdbcIO.PreparedStatementSetCaller createStringCaller() {

View on GitHub (pinned to 12126d8942)

Solutions

  1. Compare the row's schema field types with the actual database table column definitions and widen/align the column (ALTER TABLE) or truncate/coerce the value in the pipeline
  2. Catch SQLException details in the chained cause (getCause()) to identify which field/value failed
  3. For string fields, pre-validate lengths in a DoFn before JdbcIo.write()
  4. Ensure the JDBC driver version matches the database server version to avoid binding quirks

Example fix

// before
rows.apply(JdbcIO.<Row>write().withDataSourceConfiguration(cfg).withStatement(INSERT));
// after: truncate oversized strings first
rows.apply(ParDo.of(new DoFn<Row, Row>() {
  @ProcessElement public void process(ProcessContext c) {
    Row r = c.element();
    String v = r.getString(0);
    c.element(Row.fromRow(r).withValue(0, v != null && v.length() > 255 ? v.substring(0, 255) : v).build());
  }
})).apply(JdbcIO.<Row>write()...);
Defensive patterns

Strategy: validation

Validate before calling

for (int i = 0; i < row.getSchema().getFieldCount(); i++) {
  Schema.FieldType t = row.getSchema().getField(i).getType();
  Object v = row.getValue(i);
  if (v instanceof String && t.equals(Schema.FieldType.STRING)) {
    // check against known column capacity
    if (((String) v).length() > 255) throw new IllegalArgumentException("field " + i + " too long");
  }
  if (v instanceof Double && (Double.isInfinite((Double) v) || Double.isNaN((Double) v))) {
    throw new IllegalArgumentException("field " + i + " is NaN/Inf, driver may reject it");
  }
}

Type guard

boolean isBindable(Object v) {
  return v == null || v instanceof String || v instanceof Number || v instanceof Boolean
      || v instanceof java.sql.Timestamp || v instanceof java.sql.Date;
}

Try / catch

try {
  rows.apply(JdbcIO.<Row>write().withDataSourceConfiguration(cfg).withStatement(sql));
} catch (RuntimeException e) {
  if (e.getMessage().startsWith("Unable to create prepared statement for type")) {
    LOG.error("JDBC bind failed for type {}; cause: {}", e.getCause());
  }
  throw e;
}

Prevention

When it happens

Trigger: Writing a Beam row to JDBC where row field i has a type whose value cannot be bound by the driver: e.g. a string longer than the column (CHAR/VARCHAR overflow), a NaN/infinite DOUBLE on drivers that reject it, or a logical-type value incompatible with the mapped setXxx call.

Common situations: Schema drift between Beam pipeline output and database table (column narrowed after pipeline was built); inserting oversized VARCHAR/DECIMAL values; PostgreSQL rejecting infinite/NaN doubles; inserting into a column with a mismatched precision.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/f51bc8eb554b692b. Report an issue: GitHub.