pinpoint-apm/pinpoint · error · IllegalArgumentException
Unknown primaryForFieldAndTagRelation:
Error message
Unknown primaryForFieldAndTagRelation:
What it means
getLegendName derives chart legend labels from the metric definition's primaryForFieldAndTagRelation enum, which must be FIELD or TAG (the two valid cardinality anchors). When the stored/serialized value is anything else — including null from an unset or legacy record — the default branch throws IllegalArgumentException with the offending value appended. (The trailing colon with nothing after it in some logs indicates the value's toString() was null or empty.)
Solutions
- Fix the stored definition so primaryForFieldAndTagRelation is FIELD or TAG (re-save via the web UI or update the DB row).
- Normalize enum parsing upstream: reject definitions whose relation is not FIELD/TAG at save time (validate() should set/guard it).
- If data comes from an old version, run a migration/backfill setting the field for all definitions.
- For a tolerant read path, fall back to a default legend instead of throwing (see exampleFix).
Example fix
// before
default:
throw new IllegalArgumentException("Unknown primaryForFieldAndTagRelation: " + primaryForFieldAndTagRelation);
// after
default:
if (primaryForFieldAndTagRelation == null) {
return chartQueryParameter.getRawTags(); // legacy fallback
}
throw new IllegalArgumentException("Unknown primaryForFieldAndTagRelation: " + primaryForFieldAndTagRelation); Defensive patterns
Strategy: try-catch
Validate before calling
function validateRelation(def) {
if (def.primaryForFieldAndTagRelation !== 'FIELD' && def.primaryForFieldAndTagRelation !== 'TAG') {
throw new Error("primaryForFieldAndTagRelation must be FIELD or TAG, got: " + def.primaryForFieldAndTagRelation);
}
} Type guard
boolean isValidRelation(AppMetricDefinition definition) {
return definition.getPrimaryForFieldAndTagRelation() == FieldAndTagRelation.FIELD
|| definition.getPrimaryForFieldAndTagRelation() == FieldAndTagRelation.TAG;
} Try / catch
try {
String legend = getLegendName(primaryForFieldAndTagRelation, chartQueryParameter);
} catch (IllegalArgumentException e) {
log.warn("Falling back to default legend; definition relation invalid: {}", e.getMessage());
legend = chartQueryParameter.getFieldName();
} Prevention
- Enforce FIELD/TAG validity at definition save time, not only at query time.
- Backfill or migrate rows created before the enum was introduced.
- Use exact enum constants in API clients; beware case-sensitive string serialization.
- Log the full definition id when this throws so bad rows can be located quickly.
When it happens
Trigger: Querying metric data (getMetricData → getLegendName) for a metric definition whose primaryForFieldAndTagRelation field is null or an unrecognized value — e.g. data written by an older Pinpoint version before the enum existed, or a manually edited DB row / API payload with a typo like "Field" (wrong case) or "fieldAndTag".
Common situations: 1) Legacy rows in the OTLP metric-definition storage predating the FIELD/TAG enum. 2) REST clients sending lowercase/mis-spelled relation values. 3) Deserialization frameworks mapping unknown strings to null. 4) Bug where the definition was saved without computing the primary relation (validateCountOfTagAndField enforces 1:N or N:1, but the primary flag may not have been set).
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- AnnotationKey code of
- Either tagGroupList or fieldNameList must have a size of…
- Failed to detect pinpoint profile. Please add…
- list size not same
- N:N relationship between fields and tags is not allowed.
AI-assisted analysis of pinpoint-apm/pinpoint@744c3d3075 (2026-09-07).
Data as JSON: /api/errors/05d51cd37c4e20d4.
Report an issue: GitHub.
Appendix: source
Thrown at otlpmetric/otlpmetric-web/src/main/java/com/navercorp/pinpoint/otlp/web/service/OtlpMetricWebServiceImpl.java:198
}
}
return metricData;
}
private MetricData createEmptyMetricData(TimeWindow timeWindow, ChartType chartType) {
List<Long> windows = timeWindow.getTimeseriesWindows();
return new MetricData(windows, chartType, EMPTY_STRING, "There is no metadata for the metric query.");
}
private String getLegendName(OtlpMetricDataQueryParameter chartQueryParameter, PrimaryForFieldAndTagRelation primaryForFieldAndTagRelation) {
switch (primaryForFieldAndTagRelation) {
case FIELD:
return chartQueryParameter.getRawTags();
case TAG:
return chartQueryParameter.getFieldName();
default:
throw new IllegalArgumentException("Unknown primaryForFieldAndTagRelation: " + primaryForFieldAndTagRelation);
}
}
private void addMetricValue(TimeWindow timeWindow,
List<DataPoint> meticDataList, MetricData metricData, String legendName, String version){
TimeSeriesBuilder timeSeriesBuilder = new TimeSeriesBuilder(timeWindow);
List<DataPoint> metricPointList = timeSeriesBuilder.build(MetricPoints::createUnCollectedPoint, meticDataList);
List<Number> valueList = metricPointList.stream().map(DataPoint::getValue).collect(Collectors.toList());
metricData.addMetricValue(new MetricValue(legendName, valueList, version));
// if (otlpMetricChartResults != null) {
// TODO : (minwoo) Get summary data
// if ((metricData.hasSummaryField()) && (chartFieldView != null)) {
// OtlpMetricDataQueryParameter chartQueryParameter = setupQueryParameter(builder, key);View on GitHub (pinned to 744c3d3075)