{"record":{"id":"e433e1bdfb08638f","repo":"pinpoint-apm/pinpoint","slug":"invalid-linkkey-format-expected-fromapp-toapp-b","errorCode":null,"errorMessage":"Invalid linkKey format: expected 'fromApp~toApp' but got: ","messagePattern":"Invalid linkKey format: expected 'fromApp~toApp' but got: ","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"web/src/main/java/com/navercorp/pinpoint/web/applicationmap/controller/ServerMapHistogramController.java","lineNumber":360,"sourceCode":"    }\n\n    @GetMapping(value = \"/statistics/links\")\n    public LinkHistogramSummaryView getLinkTimeHistogramData(\n            @ServiceParam ServiceName serviceName,\n            @Valid @ModelAttribute\n            ApplicationForm appForm,\n            @Valid @ModelAttribute\n            RangeForm rangeForm,\n            @Valid @ModelAttribute\n            SearchOptionForm searchForm,\n            @RequestParam(\"linkKey\") @NotBlank String linkKey\n    ) {\n        final Range range = toRange(rangeForm);\n        this.rangeValidator.validate(range);\n        TimeWindow timeWindow = new TimeWindow(range);\n\n        if (!LINK_KEY_VALIDATION_PATTERN.matcher(linkKey).matches()) {\n            throw new IllegalArgumentException(\"Invalid linkKey format: expected 'fromApp~toApp' but got: \" + linkKey);\n        }\n        String[] parts = LINK_DELIMITER_PATTERN.split(linkKey, 2);\n        if (parts.length != 2) {\n            throw new IllegalArgumentException(\"Invalid linkKey format: expected 'fromApp~toApp' but got: \" + linkKey);\n        }\n        final Service service = serviceModelResolver.getService(serviceName.getName());\n        final Application fromApplication = this.newApplication(service, parts[0]);\n        final Application toApplication = this.newApplication(service, parts[1]);\n\n        final LinkHistogramSummary linkHistogramSummary =\n                histogramService.selectLinkHistogramData(fromApplication, toApplication, timeWindow);\n\n        return new LinkHistogramSummaryView(linkHistogramSummary, timeWindow, TimeHistogramView.TimeseriesHistogram);\n    }\n}\n","sourceCodeStart":342,"sourceCodeEnd":376,"githubUrl":"https://github.com/pinpoint-apm/pinpoint/blob/744c3d3075e595656abb1ae331ad2c0e4c9eb996/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/controller/ServerMapHistogramController.java#L342-L376","documentation":"ServerMapHistogramController.getLinkTimeHistogramData validates the `linkKey` request parameter against the regex ^[^~]+~[^~]+$ before parsing it into a from/to application pair. This error is thrown when the supplied linkKey does not contain exactly two '~'-separated non-empty segments (e.g. it is empty, null-ish, or lacks the '~' separator). The controller refuses to proceed because it cannot split the key into fromApp and toApp applications.","triggerScenarios":"GET/POST to the link time-histogram endpoint (getLinkTimeHistogramData) with a missing, empty, or malformed `linkKey` parameter — e.g. linkKey='', linkKey='myApp' (no '~'), or a key containing multiple '~' characters or empty sides like '~toApp' / 'fromApp~'.","commonSituations":"Frontend code building the link key by hand instead of using the node/link keys returned by the server-map API; URL-encoding issues that strip or mangle the '~'; a UI bug passing an undefined linkKey after a map refresh; API consumers guessing the key format instead of copying it verbatim from a previous server-map response.","solutions":["Pass the linkKey exactly as returned by the server-map data endpoint (format 'fromAppName~toAppName'), e.g. linkKey=FRONT~BACK","Ensure the request actually includes the linkKey parameter; empty string fails the same validation","Verify the two application names are non-empty and contain no '~' characters themselves; if an app name can contain '~', URL-encode it or pick a different delimiter path","Check the client code that constructs the key — it must join exactly two non-empty segments with a single '~'"],"exampleFix":"// before\nconst linkKey = fromApp + toApp; // missing separator\nconst url = `/getLinkTimeHistogramData?linkKey=${linkKey}`;\n// after\nconst linkKey = `${fromApp}~${toApp}`;\nif (!fromApp || !toApp) throw new Error('fromApp and toApp are required');\nconst url = `/getLinkTimeHistogramData?linkKey=${encodeURIComponent(linkKey)}`;","handlingStrategy":"validation","validationCode":"function isValidLinkKey(linkKey) {\n  return typeof linkKey === 'string' && /^[^~]+~[^~]+$/.test(linkKey);\n}\nif (!isValidLinkKey(linkKey)) throw new Error('linkKey must be fromApp~toApp');","typeGuard":"function isLinkKey(v) {\n  return typeof v === 'string' && /^[^~]+~[^~]+$/.test(v);\n}","tryCatchPattern":"try {\n  const data = await api.getLinkTimeHistogramData({ linkKey });\n} catch (e) {\n  if (e.status === 400 && String(e.message).includes('Invalid linkKey format')) {\n    // refresh linkKey from the server-map response and retry once\n  } else { throw e; }\n}","preventionTips":["Always take linkKey values directly from server-map API responses instead of constructing them manually","Validate the fromApp~toApp shape client-side before sending","URL-encode link keys in query strings so '~' and special characters survive transit","Guard against undefined/null link keys when UI state resets after map refreshes"],"tags":["validation","http-request","bad-request","link-key"],"backgroundTag":"invalid-argument-format","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"}