{"record":{"id":"57ebd7ca1b552b4b","repo":"elastic/elasticsearch","slug":"link-error","errorCode":null,"errorMessage":"link error","messagePattern":"link error","errorType":"exception","errorClass":"ScriptException","httpStatus":null,"severity":"error","filePath":"modules/lang-expression/src/main/java/org/elasticsearch/script/expression/ExpressionScriptEngine.java","lineNumber":396,"sourceCode":"        return new ExpressionScoreScript(expr, bindings, needsScores);\n    }\n\n    /**\n     * converts a ParseException at compile-time or link-time to a ScriptException\n     */\n    private static ScriptException convertToScriptException(String message, String source, String portion, Throwable cause) {\n        List<String> stack = new ArrayList<>();\n        stack.add(portion);\n        StringBuilder pointer = new StringBuilder();\n        if (cause instanceof ParseException) {\n            int offset = ((ParseException) cause).getErrorOffset();\n            for (int i = 0; i < offset; i++) {\n                pointer.append(' ');\n            }\n        }\n        pointer.append(\"^---- HERE\");\n        stack.add(pointer.toString());\n        throw new ScriptException(message, cause, stack, source, NAME);\n    }\n\n    private static DoubleValuesSource getDocValueSource(String variable, SearchLookup lookup) throws ParseException {\n        VariableContext[] parts = VariableContext.parse(variable);\n        if (parts[0].text.equals(\"doc\") == false) {\n            throw new ParseException(\"Unknown variable [\" + parts[0].text + \"]\", 0);\n        }\n        if (parts.length < 2 || parts[1].type != VariableContext.Type.STR_INDEX) {\n            throw new ParseException(\"Variable 'doc' must be used with a specific field like: doc['myfield']\", 3);\n        }\n\n        // .value is the default for doc['field'], its optional.\n        String variablename = \"value\";\n        String methodname = null;\n        if (parts.length == 3) {\n            if (parts[2].type == VariableContext.Type.METHOD) {\n                methodname = parts[2].text;\n            } else if (parts[2].type == VariableContext.Type.MEMBER) {","sourceCodeStart":378,"sourceCodeEnd":414,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/ExpressionScriptEngine.java#L378-L414","documentation":"Thrown by convertToScriptException with message \"link error\" during the variable binding phase in newSortScript, newScoreScript, newAggregationScript, newFieldScript, newTermsSetQueryScript. After compilation succeeds, each variable in the expression is bound to a DoubleValuesSource; if binding fails (field doesn't exist, wrong type, param not numeric), the exception is caught and re-thrown as a ScriptException with \"link error\" and the offending variable name as the stack portion.","triggerScenarios":"Expression compiles successfully but references a doc['fieldname'] where 'fieldname' does not exist in the mapping, is not numeric/date/geo, or a param value is non-numeric. Example: expression \"doc['nonexistent'].value\" passes parsing but fails at binding time.","commonSituations":"Field renamed or removed from mapping without updating scripts; typo in field name inside the expression; field exists but is of type text/keyword (not supported by expressions); param value type mismatch.","solutions":["Read the ScriptException stack — the portion line shows which variable failed to bind.","Verify the field exists in the index mapping: GET <index>/_mapping.","Confirm the field type is numeric (integer, long, float, double, scaled_float), date, or geo_point — text and keyword are not supported.","If the variable is a param, ensure its value is numeric (see error 1237)."],"exampleFix":"// before: field 'price' does not exist or is type text\n\"source\": \"doc['price'].value\"\n// after: verify mapping and use correct field\nGET my-index/_mapping\n// if field is missing, add it or use the correct name\n\"source\": \"doc['amount'].value\"","handlingStrategy":"validation","validationCode":"// Before using an expression, validate that all doc['field'] references exist and are the right type\nGET my-index/_mapping\n// For each doc['fieldname'] in the expression, confirm:\n//   1. fieldname exists in the mapping\n//   2. field type is numeric, date, or geo_point\n//   3. doc_values is not disabled","typeGuard":null,"tryCatchPattern":"try {\n    engine.compile(scriptName, scriptSource, context, params);\n} catch (ScriptException e) {\n    if (\"link error\".equals(e.getScriptStack().get(0)) || e.getMessage().contains(\"link error\")) {\n        // The portion line in the stack names the variable that failed to bind\n        logger.error(\"Expression link error for variable: {}\", e.getScriptStack());\n        // Check mapping, field type, and param types\n    }\n    throw e;\n}","preventionTips":["Always validate field names against the index mapping before deploying expressions.","Confirm field types are numeric, date, or geo_point — text/keyword/boolean are not supported.","When a mapping changes, audit all expression scripts that reference the changed fields.","Use the link error's variable name in the ScriptException stack to identify which field failed."],"tags":["expression","script","compile","binding","field-mapping"],"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T06:17:24.410Z"}