apache/seatunnel · error · SensorsDataException
ILLEGAL_ARGUMENT
ILLEGAL_ARGUMENT
Error message
Identity value must be String or List. [field=%s]
What it means
RowAccessor.getIdentityValue() accepts identity values only as String (used for $identity_login_id) or List (used for all other identity fields); anything else makes getDistinctId() throw SensorsDataException(ILLEGAL_ARGUMENT). This fires when TypeUtil.toTargetType(...) produced a value of an unexpected runtime type for an identity column, so the distinct_id fallback derived from identity fields cannot be stringified.
Source
Thrown at seatunnel-connectors-v2/connector-sensorsdata/src/main/java/org/apache/seatunnel/connectors/sensorsdata/format/record/RowAccessor.java:219
}
return userIdentities.entrySet().stream()
.findFirst()
.map(
it ->
String.format(
"%s+%s",
it.getKey(), getIdentityValue(it.getKey(), it.getValue())))
.orElse(null);
}
private String getIdentityValue(String field, Object value) {
if (value instanceof List) {
return ((List) value).get(0).toString();
} else if (value instanceof String) {
return (String) value;
}
throw new SensorsDataException(
SensorsDataErrorCode.ILLEGAL_ARGUMENT,
String.format("Identity value must be String or List. [field=%s]", field));
}
public Map<String, Object> getUserIdentities(SeaTunnelRow row) {
Map<String, Object> identities = new HashMap<>();
for (TargetColumnConfig col : config.getIdentityFields()) {
String key = col.getTarget();
int index = columnIndex.get(col.getSource());
Object strValue =
TypeUtil.toTargetType(row.getField(index), SensorsDataTypes.DataTypes.STRING);
// if the value is null or blank, skip it
if (strValue == null || StringUtils.isBlank((String) strValue)) {
continue;
}View on GitHub (pinned to cf67b549a7)
Solutions
- Ensure each identity_fields entry maps a source column of STRING type; the connector relies on TypeUtil.toTargetType(..., STRING) or (... , LIST) producing String/List respectively.
- If the identity is $identity_login_id, use exactly that target key so it is parsed as STRING; any other key is parsed as LIST.
- Null-check/validate identity column values upstream — blank values are skipped, but non-String/non-List values reach this throw.
- If you are developing the connector, check TypeUtil.toTargetType for the column's declared type and make LIST conversion cover the types your users supply.
Example fix
// before (job config)
identity_fields {
source = "user_id"
target = "$identity_user_id"
type = BIGINT // converted value is a number, not String/List
}
// after
identity_fields {
source = "user_id"
target = "$identity_user_id"
type = STRING
} Defensive patterns
Strategy: type-guard
Validate before calling
// verify identity column values are strings before the sink
rows.forEach(r -> {
Object v = r.get(identityCol);
if (v != null && !(v instanceof String) && !(v instanceof List)) {
throw new IllegalArgumentException(
"identity column " + identityCol + " must be String or List, got " + v.getClass());
}
}); Type guard
boolean isStringOrList(Object v) {
return v instanceof String || v instanceof List;
} Try / catch
try {
record = builder.build(row);
} catch (SensorsDataException e) {
if (SensorsDataErrorCode.ILLEGAL_ARGUMENT.equals(e.getErrorCode())) {
log.warn("Skipping row with bad identity value: {}", row);
return;
}
throw e;
} Prevention
- Declare identity_fields source columns as STRING type in the source schema.
- Use the exact $identity_login_id key for login ids so they are parsed as strings.
- Validate identity column types when the source schema changes.
- Keep TypeUtil LIST-conversion coverage in sync with supported column types if developing the connector.
When it happens
Trigger: config.distinct_id_by_identities=true and the distinct_id column value is null/blank, so getDistinctId(getUserIdentities(row)) falls back to identity fields; one of those identity columns is configured to be parsed as neither String nor List (e.g., the identity target key resolves to isLoginId()==false but TypeUtil returns a non-LIST object), or TypeUtil.toTargetType returns an Integer/Map for that column's declared type.
Common situations: identity_fields entry whose source column has a numeric type while the conversion path produced a raw number instead of a LIST wrapper; a custom/patched TypeUtil that skips the LIST conversion; adding a new identity target key whose mapping logic doesn't hit the login-id branch.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- EVENT_NAME_NOT_SET
- MISSING_NECESSARY_FIELD
- UNSUPPORTED_RECORD_TYPE
- Unsupported convert %s to Map, typeDefine: %s
- Unsupported convert %s to Array, typeDefine: %s
AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10).
Data as JSON: /api/errors/d661e371a3d8b61e.
Report an issue: GitHub.