{"record":{"id":"d661e371a3d8b61e","repo":"apache/seatunnel","slug":"illegal-argument-d661e3","errorCode":"ILLEGAL_ARGUMENT","errorMessage":"Identity value must be String or List. [field=%s]","messagePattern":"Identity value must be String or List\\. \\[field=(.+?)\\]","errorType":"error_code","errorClass":"SensorsDataException","httpStatus":null,"severity":"error","filePath":"seatunnel-connectors-v2/connector-sensorsdata/src/main/java/org/apache/seatunnel/connectors/sensorsdata/format/record/RowAccessor.java","lineNumber":219,"sourceCode":"        }\n\n        return userIdentities.entrySet().stream()\n                .findFirst()\n                .map(\n                        it ->\n                                String.format(\n                                        \"%s+%s\",\n                                        it.getKey(), getIdentityValue(it.getKey(), it.getValue())))\n                .orElse(null);\n    }\n\n    private String getIdentityValue(String field, Object value) {\n        if (value instanceof List) {\n            return ((List) value).get(0).toString();\n        } else if (value instanceof String) {\n            return (String) value;\n        }\n        throw new SensorsDataException(\n                SensorsDataErrorCode.ILLEGAL_ARGUMENT,\n                String.format(\"Identity value must be String or List. [field=%s]\", field));\n    }\n\n    public Map<String, Object> getUserIdentities(SeaTunnelRow row) {\n        Map<String, Object> identities = new HashMap<>();\n\n        for (TargetColumnConfig col : config.getIdentityFields()) {\n            String key = col.getTarget();\n            int index = columnIndex.get(col.getSource());\n\n            Object strValue =\n                    TypeUtil.toTargetType(row.getField(index), SensorsDataTypes.DataTypes.STRING);\n\n            // if the value is null or blank, skip it\n            if (strValue == null || StringUtils.isBlank((String) strValue)) {\n                continue;\n            }","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/apache/seatunnel/blob/cf67b549a7a6c35fa0beb12d83c62892427ea919/seatunnel-connectors-v2/connector-sensorsdata/src/main/java/org/apache/seatunnel/connectors/sensorsdata/format/record/RowAccessor.java#L201-L237","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (job config)\nidentity_fields {\n  source = \"user_id\"\n  target = \"$identity_user_id\"\n  type = BIGINT   // converted value is a number, not String/List\n}\n// after\nidentity_fields {\n  source = \"user_id\"\n  target = \"$identity_user_id\"\n  type = STRING\n}","handlingStrategy":"type-guard","validationCode":"// verify identity column values are strings before the sink\nrows.forEach(r -> {\n    Object v = r.get(identityCol);\n    if (v != null && !(v instanceof String) && !(v instanceof List)) {\n        throw new IllegalArgumentException(\n            \"identity column \" + identityCol + \" must be String or List, got \" + v.getClass());\n    }\n});","typeGuard":"boolean isStringOrList(Object v) {\n    return v instanceof String || v instanceof List;\n}","tryCatchPattern":"try {\n    record = builder.build(row);\n} catch (SensorsDataException e) {\n    if (SensorsDataErrorCode.ILLEGAL_ARGUMENT.equals(e.getErrorCode())) {\n        log.warn(\"Skipping row with bad identity value: {}\", row);\n        return;\n    }\n    throw e;\n}","preventionTips":["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."],"tags":["sensorsdata","seatunnel","type-mismatch","identity"],"backgroundTag":"type-mismatch","analyzedSha":"cf67b549a7a6c35fa0beb12d83c62892427ea919","analyzedAt":"2026-09-10T21:44:55.265Z","contentChangedAt":"2026-09-10T21:44:55.265Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}