{"record":{"id":"4909a4ce127e2f7f","repo":"pinpoint-apm/pinpoint","slug":"either-taggrouplist-or-fieldnamelist-must-have-a-s","errorCode":null,"errorMessage":"Either tagGroupList or fieldNameList must have a size of exactly one.","messagePattern":"Either tagGroupList or fieldNameList must have a size of exactly one\\.","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"otlpmetric/otlpmetric-common/src/main/java/com/navercorp/pinpoint/otlp/common/web/defined/AppMetricDefinitionUtil.java","lineNumber":46,"sourceCode":" */\npublic class AppMetricDefinitionUtil {\n\n    static public void validate(List<AppMetricDefinition> appMetricDefinitionList) {\n        for (AppMetricDefinition appMetricDefinition : appMetricDefinitionList) {\n            List<String> tagGroupList = appMetricDefinition.getTagGroupList();\n            List<String> fieldNameList = appMetricDefinition.getFieldNameList();\n\n            validateCountOfTagAndField(tagGroupList, fieldNameList);\n        }\n    }\n\n    static public void validateCountOfTagAndField(List<String> tagGroupList, List<String> fieldNameList) {\n        if (tagGroupList.size() > 1 && fieldNameList.size() > 1) {\n            throw new IllegalArgumentException(\"N:N relationship between fields and tags is not allowed.\");\n        }\n\n        if (tagGroupList.size() != 1 && fieldNameList.size() != 1) {\n            throw new IllegalArgumentException(\"Either tagGroupList or fieldNameList must have a size of exactly one.\");\n        }\n    }\n\n    static public void generateAndSetUniqueId(List<AppMetricDefinition> appMetricDefinitionList) {\n        Set<String> existingIds = appMetricDefinitionList.stream()\n                .map(AppMetricDefinition::getId)\n                .filter(StringUtils::hasLength)\n                .collect(Collectors.toSet());\n\n        appMetricDefinitionList.stream().filter(appMetricDefinition -> StringUtils.isEmpty(appMetricDefinition.getId()))\n                .forEach(definition -> {\n                    String newId;\n\n                    do {\n                        newId = UUID.randomUUID().toString().substring(0, 8);\n                    } while (!existingIds.add(newId));\n\n                    definition.setId(newId);","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/pinpoint-apm/pinpoint/blob/744c3d3075e595656abb1ae331ad2c0e4c9eb996/otlpmetric/otlpmetric-common/src/main/java/com/navercorp/pinpoint/otlp/common/web/defined/AppMetricDefinitionUtil.java#L28-L64","documentation":"After ruling out N:N, validateCountOfTagAndField still requires that at least one of tagGroupList or fieldNameList has exactly one element — the relation must be anchored on one side. If both lists are empty, or both sizes differ from 1 in the degenerate case (e.g. both size 0), IllegalArgumentException is thrown. (Note: an empty list combined with a list of size >1 can also reach this check since the first guard only catches the >1/>1 case.)","triggerScenarios":"Saving/validating an AppMetricDefinition where tagGroupList and fieldNameList are both empty, or where neither list has exactly one element (e.g. tagGroupList=[] and fieldNameList=[], or tagGroupList=[] and fieldNameList has 2+ entries).","commonSituations":"1) A metric-definition POST with missing/empty `tagGroup` and `fieldName` arrays. 2) UI state where the user selected no fields and no tag grouping but submitted anyway. 3) Deserialization of JSON that dropped empty defaults leaving both lists at size 0.","solutions":["Ensure exactly one side is anchored: pass a single-element tagGroupList OR a single-element fieldNameList (the other side may have 1..n elements).","Validate the definition before submission: require tagGroupList.size()==1 || fieldNameList.size()==1.","Fix client code that builds lists conditionally so it never submits both-empty definitions.","If a >1/>1 pair that passed the first guard is hitting this, split into multiple definitions as with the N:N error."],"exampleFix":"// before\nvalidateDefinition(tagGroups=[], fieldNames=[])  // throws\n// after\nif (tagGroups.size() == 1 || fieldNames.size() == 1) { save(tagGroups, fieldNames); }\nelse throw new IllegalArgumentException(\"Provide exactly one tagGroup or exactly one fieldName\");","handlingStrategy":"validation","validationCode":"if ((def.tagGroups?.length ?? 0) === 0 && (def.fieldNames?.length ?? 0) === 0) {\n  throw new Error(\"Metric definition requires at least one tagGroup or one fieldName\");\n}\nif (!((def.tagGroups?.length ?? 0) === 1 || (def.fieldNames?.length ?? 0) === 1)) {\n  throw new Error(\"Exactly one side (tagGroup or fieldName) must be a single element\");\n}","typeGuard":"boolean hasAnchorSide(List<String> tagGroups, List<String> fieldNames) {\n    return tagGroups.size() == 1 || fieldNames.size() == 1;\n}","tryCatchPattern":"try {\n    validate(def);\n} catch (IllegalArgumentException e) {\n    log.warn(\"Rejecting metric definition: {}\", e.getMessage());\n    return 400;\n}","preventionTips":["Never submit definitions with empty tagGroup and fieldName lists.","Make the UI require at least one field and one tag grouping selection before save.","Use list-building code that guarantees non-empty output, or fail fast at construction.","Round-trip test saved definitions through validate() in CI."],"tags":["java","validation","pinpoint","metric-definition","cardinality"],"backgroundTag":"invalid-argument-value","analyzedSha":"744c3d3075e595656abb1ae331ad2c0e4c9eb996","analyzedAt":"2026-09-07T18:48:45.289Z","contentChangedAt":"2026-09-07T18:48:45.289Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}