apache/seatunnel · error · BigtableConnectorException

WRITE_FAILED

WRITE_FAILED

Error message

Row key cannot be empty. Check rowkey_column configuration.

What it means

BigtableSinkWriter.convertRowToMutation() builds the row key from the configured rowkey_column and refuses to emit a mutation when the resulting ByteString is empty. Bigtable requires a non-empty row key for every mutation, so an empty key indicates a configuration or data problem rather than a writable row.

Source

Thrown at seatunnel-connectors-v2/connector-google-bigtable/src/main/java/org/apache/seatunnel/connectors/seatunnel/bigtable/sink/BigtableSinkWriter.java:158

            if (bigtableClient != null) {
                bigtableClient.close();
            }
        }
    }

    private void flush() {
        if (buffer.isEmpty()) {
            return;
        }
        List<RowKeyMutation> toFlush = new ArrayList<>(buffer);
        buffer.clear(); // clear first: prevents re-sending if bulkMutate throws
        bigtableClient.bulkMutate(toFlush);
    }

    private RowKeyMutation convertRowToMutation(SeaTunnelRow row) {
        ByteString rowKey = buildRowKey(row);
        if (rowKey.isEmpty()) {
            throw new BigtableConnectorException(
                    BigtableConnectorErrorCode.WRITE_FAILED,
                    "Row key cannot be empty. Check rowkey_column configuration.");
        }

        long timestamp = System.currentTimeMillis() * 1000L; // Bigtable uses microseconds
        if (versionColumnIndex != -1) {
            Object versionField = row.getField(versionColumnIndex);
            if (versionField instanceof Long) {
                timestamp = (Long) versionField;
            }
        }

        Mutation mutation = Mutation.create();

        List<Integer> writeColumnIndexes =
                IntStream.range(0, row.getArity())
                        .boxed()
                        .filter(idx -> !rowkeyColumnIndexes.contains(idx))

View on GitHub (pinned to cf67b549a7)

Solutions

  1. Fix the rowkey_column configuration so it references a non-nullable, always-populated column.
  2. Filter out or coalesce rows with null/empty key values upstream (e.g. a Filter or SQL transform) before the sink.
  3. Use a composite or derived rowkey expression that guarantees non-empty output (e.g. concatenate with a constant).

Example fix

// before
rowkey_column = "optional_id"
// after
rowkey_column = "id" // or a transform: id = coalesce(optional_id, default_id)
Defensive patterns

Strategy: validation

Validate before calling

// before writing, per row
Object keyVal = row.getField(rowkeyFieldIndex);
if (keyVal == null || keyVal.toString().isEmpty()) {
  throw new IllegalArgumentException("Row key field is null/empty for row: " + row);
}

Try / catch

try {
  sink.write(row);
} catch (BigtableConnectorException e) {
  if (e.getMessage().contains("Row key cannot be empty")) {
    log.error("Skipping row with empty key: {}", row); // or dead-letter
  } else throw e;
}

Prevention

When it happens

Trigger: A SeaTunnelRow reaches the writer where buildRowKey(row) produces an empty ByteString — e.g. rowkey_column points to a null/empty field, or the rowkey expression evaluates to nothing for that row.

Common situations: rowkey_column referencing a nullable source column that is null in some rows; a missing or mistyped rowkey_column config key so the key expression resolves to empty; source data with empty string keys.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10). Data as JSON: /api/errors/890e05ff1661eda0. Report an issue: GitHub.