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

  1. 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.
  2. 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.
  3. Null-check/validate identity column values upstream — blank values are skipped, but non-String/non-List values reach this throw.
  4. 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

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


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