elasticsearch
Loading

Painless debugging

Serverless Stack

Painless scripts run within specific Elasticsearch contexts, not as isolated code. Unlike languages with interactive environments, Painless doesn’t provide a REPL because script behavior depends entirely on the execution context and available data structures.

The context determines available variables, API restrictions, and expected return types. A debugging approach that works in one context might not be directly applied to another because each context provides different capabilities and data access patterns. Refer to Painless contexts to understand what variables and methods are available in each context.

The secure, sandboxed approach used in Painless prevents access to standard Java debugging information. The sandbox restricts scripts from accessing information like object types, which has the effect of preventing traditional Java debugging methods.

Scripts cannot access:

  • Object type information through standard Java methods (like get_Class() or reflection)
  • Stack traces beyond Painless script boundaries
  • Runtime class details for security-sensitive operations

Painless scripts do not provide interactive debugging tools or a REPL. The only official method for inspecting objects and their types during script execution is by means of theDebug.explain(object). This method throws an informative exception that reveals the object's type and value. As a result, your script does not complete normally; instead, you an error message is included in the response.

You can use custom exceptions (for example, throw new RuntimeException(...)) to signal specific conditions or control execution flow but do not provide detailed object inspection. For comprehensive debugging, always use Debug.explain().

This section demonstrates a common debugging scenario using a script that formats the total price as "TOTAL: X" in uppercase. The example shows how to identify an error, debug it using Debug.explain(), and apply the fix.

The following script attempts to create a formatted price string from the ecommerce dataset. The script appears logical but fails:

GET /kibana_sample_data_ecommerce/_search
{
  "size": 1,
  "query": {
    "match_all": {}
  },
  "script_fields": {
    "formatted_total": {
      "script": {
        "source": """
          // This will fail - trying to call toUpperCase() on a number
          return "TOTAL: " + doc['taxful_total_price'].toUpperCase();
        """
      }
    }
  }
}
		

The script fails with this error message:

{
  "took": 8,
  "timed_out": false,
  "_shards": {
    "total": 3,
    "successful": 2,
    "skipped": 0,
    "failed": 1,
    "failures": [
      {
        "shard": 0,
        "index": "kibana_sample_data_ecommerce",
        "node": "Rcc7GqOuTta0MV28FZXE_A",
        "reason": {
          "type": "script_exception",
          "reason": "runtime error",
          "script_stack": [
            """return "TOTAL: " + doc['taxful_total_price'].toUpperCase();
            """,
            "                                             ^---- HERE"
          ],
          "script": " ...",
          "lang": "painless",
          "position": {
            "offset": 62,
            "start": 14,
            "end": 90
          },
          "caused_by": {
            "type": "illegal_argument_exception",
            "reason": "dynamic method [java.lang.Double, toUpperCase/0] not found"
          }
        }
      }
    ]
  },
  "hits": {
    "total": {
      "value": 4675,
      "relation": "eq"
    },
    "max_score": 1,
    "hits": []
  }
}
		

The error indicates that the script attempts to call toUpperCase() on a Double object, but this method does not exist for numeric types. While the error message provides information about the problem; use Debug.explain() to gain additional clarity about the data type.

Use Debug.explain() to inspect the data type:

GET /kibana_sample_data_ecommerce/_search
{
  "size": 1,
  "query": {
    "match_all": {}
  },
  "script_fields": {
    "debug_price_type": {
      "script": {
        "source": """
          Debug.explain(doc['taxful_total_price']);
          return "Check the error for debugging information.";
        """
      }
    }
  }
}
		

The debugging output reveals the data structure:

{
  "took": 9,
  "timed_out": false,
  "_shards": {
    "total": 3,
    "successful": 2,
    "skipped": 0,
    "failed": 1,
    "failures": [
      {
        "shard": 0,
        "index": "kibana_sample_data_ecommerce",
        "node": "Rcc7GqOuTta0MV28FZXE_A",
        "reason": {
          "type": "script_exception",
          "reason": "runtime error",
          "painless_class": "org.elasticsearch.index.fielddata.ScriptDocValues$Doubles",
          "to_string": "[46.96875]",
          "java_class": "org.elasticsearch.index.fielddata.ScriptDocValues$Doubles",
          "script_stack": [
            """Debug.explain(doc['taxful_total_price']);
          """,
            "                                       ^---- HERE"
          ],
          "script": " ...",
          "lang": "painless",
          "position": {
            "offset": 46,
            "start": 11,
            "end": 62
          },
          "caused_by": {
            "type": "painless_explain_error",
            "reason": null
          }
        }
      }
    ]
  },
  "hits": {
    "total": {
      "value": 4675,
      "relation": "eq"
    },
    "max_score": 1,
    "hits": []
  }
}
		

The output shows that doc['taxful_total_price'] is an org.elasticsearch.index.fielddata.ScriptDocValues$Doubles object with the value [46.96875]. This is an Elasticsearch class for handling numeric field values.
To access the actual double value, use doc['taxful_total_price'].value.

Check the actual value:

GET /kibana_sample_data_ecommerce/_search
{
  "size": 1,
  "query": {
    "match_all": {}
  },
  "script_fields": {
    "debug_price_value": {
      "script": {
        "source": """
          Debug.explain(doc['taxful_total_price'].value);
          return "Check the error for debugging information.";
        """
      }
    }
  }
}
		

This produces the following output:

{
  "took": 5,
  "timed_out": false,
  "_shards": {
    "total": 3,
    "successful": 2,
    "skipped": 0,
    "failed": 1,
    "failures": [
      {
        "shard": 0,
        "index": "kibana_sample_data_ecommerce",
        "node": "Rcc7GqOuTta0MV28FZXE_A",
        "reason": {
          "type": "script_exception",
          "reason": "runtime error",
          "painless_class": "java.lang.Double",
          "to_string": "46.96875",
          "java_class": "java.lang.Double",
          "script_stack": [
            """Debug.explain(doc['taxful_total_price'].value);
          """,
            "                                             ^---- HERE"
          ],
          "script": " ...",
          "lang": "painless",
          "position": {
            "offset": 46,
            "start": 11,
            "end": 62
          },
          "caused_by": {
            "type": "painless_explain_error",
            "reason": null
          }
        }
      }
    ]
  },
  "hits": {
    "total": {
      "value": 4675,
      "relation": "eq"
    },
    "max_score": 1,
    "hits": []
  }
}
		

The output confirms that doc['taxful_total_price'].value is a java.lang.Double with the value of 46.96875.

This demonstrates the problem: the script attempts to call toUpperCase() on a number, which is not supported.

Apply the fix by converting the number to a string first:

GET /kibana_sample_data_ecommerce/_search
{
  "size": 1,
  "query": {
    "match_all": {}
  },
  "script_fields": {
    "formatted_total": {
      "script": {
        "source": """
          // Convert the price to String before applying toUpperCase()
          return "TOTAL: " + String.valueOf(doc['taxful_total_price'].value).toUpperCase();
        """
      }
    }
  }
}
		

The corrected script now executes successfully and returns the expected result:

{
  "took": 14,
  "timed_out": false,
  "_shards": {
    "total": 3,
    "successful": 3,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 4675,
      "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      {
        "_index": "kibana_sample_data_ecommerce",
        "_id": "z_vDyZgBvpJrRKrKcvig",
        "_score": 1,
        "fields": {
          "formatted_total": [
            "TOTAL: 46.96875"
          ]
        }
      }
    ]
  }
}
		
  • Data types in Painless: Numeric fields in Elasticsearch are represented as Java numeric types (Double, Integer), not as String.
  • Field access in Painless: When accessing a single-valued numeric field like taxful_total_price, use .value to get the actual numeric value.
  • Debug.explain() reveals object details: Shows both the type of object (java.lang.Double) and its actual value (46.96875), which is useful for understanding how to work with it.
  • Type conversion: Convert data types appropriately before applying type-specific methods.