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
- Fix the rowkey_column configuration so it references a non-nullable, always-populated column.
- Filter out or coalesce rows with null/empty key values upstream (e.g. a Filter or SQL transform) before the sink.
- 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
- Pick a non-nullable primary column for rowkey_column.
- Add a filter transform to drop null-key rows before the sink.
- Unit-test your rowkey expression against sample data including nulls.
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
- WRITER_OPERATION_FAILED
- TABLE_QUERY_FAILED
- FLUSH_DATA_FAILED
- FLUSH_DATA_FAILED
- TABLE_DATASET_WRITE_ST_ROW_EXCEPTION
AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10).
Data as JSON: /api/errors/890e05ff1661eda0.
Report an issue: GitHub.