{"record":{"id":"f0fdda147b0cbabf","repo":"languagetool-org/languagetool","slug":"could-not-map-plaintextposition-to-original-pos","errorCode":null,"errorMessage":"Could not map ${plainTextPosition} to original position. isToPos: ${isToPos}, ${msg}","messagePattern":"Could not map (.+?) to original position\\. isToPos: (.+?), (.+?)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"languagetool-core/src/main/java/org/languagetool/markup/AnnotatedText.java","lineNumber":131,"sourceCode":"    if (mapping.isEmpty()) {\n      return 0;\n    }\n    int minDiff = Integer.MAX_VALUE;\n    MappingValue bestMatch = null;\n    // algorithm: find the closest lower position\n    for (Map.Entry<Integer, MappingValue> entry : mapping.entrySet()) {\n      int maybeClosePosition = entry.getKey();\n      if (plainTextPosition < maybeClosePosition) {\n        int diff = maybeClosePosition - plainTextPosition;\n        if (diff > 0 && diff < minDiff) {\n          bestMatch = entry.getValue();\n          minDiff = diff;\n        }\n      }\n    }\n    if (bestMatch == null) {\n      String msg = \"mappings: \" + (mapping.size() < 5 ? mapping : mapping.size());\n      throw new RuntimeException(\"Could not map \" + plainTextPosition + \" to original position. isToPos: \" + isToPos + \", \" + msg);\n    }\n    // we remove markup total length if usage of fake markup and need from position\n    if (!isToPos && bestMatch.getFakeMarkupLength() > 0) {\n      minDiff = bestMatch.getFakeMarkupLength();\n    }\n    // We assume that when we have found the closest match there's a one-to-one mapping\n    // in this region, thus we can subtract 'minDiff' to get the exact position.\n    // If the bestMatch is a fakeMarkup, subtract it:\n    return bestMatch.getTotalPosition() - minDiff;\n  }\n  \n  /**\n   * @since 3.9\n   */\n  public String getGlobalMetaData(String key, String defaultValue) {\n    return customMetaData.getOrDefault(key, defaultValue);\n  }\n","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-core/src/main/java/org/languagetool/markup/AnnotatedText.java#L113-L149","documentation":"After searching the markup-mapping for the entry closest to the requested plain-text position, getOriginalTextPositionFor() throws if no mapping entry matched at all (bestMatch == null). This means the internal mapping between plain text and original markup is empty or inconsistent with the requested position, so no original position can be produced.","triggerScenarios":"Calling getOriginalTextPositionFor() on an AnnotatedText whose mapping produced no best match for plainTextPosition — e.g. the mapping is empty but not caught earlier, or position lies outside the mapped region and closest-match logic fails; thrown with isToPos and a debug dump of mappings (contents if <5 entries, else size).","commonSituations":"Rule match offsets computed against a different text object than the one being mapped (mismatched plain text vs markup); bugs in AnnotatedTextBuilder usage where text was added without matching markup entries.","solutions":["Ensure rule-match offsets come from the same AnnotatedText/AnnotatedTextBuilder instance that is being checked.","Verify the text was built via AnnotatedTextBuilder (addMarkup/addText) so the mapping is populated consistently.","Guard against mapping/empty or out-of-range positions before calling the API.","If it comes from a plugin/integration, upgrade LanguageTool — mapping logic changed across versions."],"exampleFix":"// before\nint orig = annotatedText.getOriginalTextPositionFor(ruleMatch.getFromPos(), false);\n// after: build and use the same text instance for matching and mapping\nAnnotatedText at = new AnnotatedTextBuilder().addText(\"...\").build();\nList<RuleMatch> ms = langTool.check(at);\nint orig = at.getOriginalTextPositionFor(ms.get(0).getFromPos(), false);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  int orig = text.getOriginalTextPositionFor(pos, false);\n} catch (RuntimeException e) {\n  if (e.getMessage().startsWith(\"Could not map\")) {\n    log.warn(\"Offset mapping failed; check text/mapping consistency\", e);\n  }\n}","preventionTips":["Use the same AnnotatedText instance for checking and offset mapping","Build text via AnnotatedTextBuilder so the mapping is populated","Upgrade LanguageTool if mapping bugs are suspected"],"tags":["offset-mapping","annotation","internal-state"],"backgroundTag":"internal-invariant-violation","analyzedSha":"2e990059ce67d5e2a0f7f7ca5d31160c6709df4b","analyzedAt":"2026-09-06T09:20:17.015Z","contentChangedAt":"2026-09-06T09:20:17.015Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}