This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 4385357bc2b0 CAMEL-26234: camel-elasticsearch-rest-client - SEARCH 
sends a JSON body as the query, GET_BY_ID and DELETE prefer the 
CamelElasticsearchId header to the body, and the doc names the real headers 
(#29076)
4385357bc2b0 is described below

commit 4385357bc2b0d8034d1ed69fec24b1d134e19a88
Author: Andrea Cosentino <[email protected]>
AuthorDate: Tue Nov 3 12:01:47 2026 +0100

    CAMEL-26234: camel-elasticsearch-rest-client - SEARCH sends a JSON body as 
the query, GET_BY_ID and DELETE prefer the CamelElasticsearchId header to the 
body, and the doc names the real headers (#29076)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../components/elasticsearch-rest-client.json      |   2 +-
 .../docs/elasticsearch-rest-client-component.adoc  |  31 ++--
 .../rest/client/elasticsearch-rest-client.json     |   2 +-
 .../docs/elasticsearch-rest-client-component.adoc  |  31 ++--
 .../client/ElasticsearchRestClientEndpoint.java    |  14 +-
 .../client/ElasticsearchRestClientProducer.java    |  23 +--
 .../ElasticsearchRestClientProducerBodyTest.java   | 189 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  11 ++
 8 files changed, 265 insertions(+), 38 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/elasticsearch-rest-client.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/elasticsearch-rest-client.json
index c5f30ddb187a..2ee6f4690977 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/elasticsearch-rest-client.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/elasticsearch-rest-client.json
@@ -47,7 +47,7 @@
     "CamelElasticsearchOperation": { "index": 4, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": 
"org.apache.camel.component.elasticsearch.rest.client.ElasticsearchRestClientOperation",
 "enum": [ "INDEX_OR_UPDATE", "GET_BY_ID", "DELETE", "CREATE_INDEX", 
"DELETE_INDEX", "SEARCH" ], "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The operation to perform", 
"constantName": "org.apache.came [...]
   },
   "bodyTypes": {
-    "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "value", 
"map", "null" ], "byOption": [ { "option": "operation", "value": 
"INDEX_OR_UPDATE", "carriers": [ "text", "bytes", "stream", "file" ] }, { 
"option": "operation", "value": "GET_BY_ID", "carriers": [ "text", "value", 
"null" ] }, { "option": "operation", "value": "DELETE", "carriers": [ "text", 
"value", "null" ] }, { "option": "operation", "value": "SEARCH", "carriers": [ 
"map", "null" ] }, { "option": "operation" [...]
+    "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "value", 
"map", "null" ], "byOption": [ { "option": "operation", "value": 
"INDEX_OR_UPDATE", "carriers": [ "text", "bytes", "stream", "file" ] }, { 
"option": "operation", "value": "GET_BY_ID", "carriers": [ "text", "value", 
"null" ] }, { "option": "operation", "value": "DELETE", "carriers": [ "text", 
"value", "null" ] }, { "option": "operation", "value": "SEARCH", "carriers": [ 
"text", "bytes", "stream", "file", "map",  [...]
     "producerOut": { "carriers": [ "text", "value" ], "byOption": [ { 
"option": "operation", "value": "INDEX_OR_UPDATE", "carriers": [ "text" ], 
"javaType": "java.lang.String" }, { "option": "operation", "value": 
"GET_BY_ID", "carriers": [ "text" ], "javaType": "java.lang.String" }, { 
"option": "operation", "value": "SEARCH", "carriers": [ "text" ], "javaType": 
"java.lang.String" }, { "option": "operation", "value": "DELETE", "carriers": [ 
"value" ], "javaType": "java.lang.Boolean" }, {  [...]
   },
   "properties": {
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/elasticsearch-rest-client-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/elasticsearch-rest-client-component.adoc
index 50d794edd785..2f920a8bce15 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/elasticsearch-rest-client-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/elasticsearch-rest-client-component.adoc
@@ -49,16 +49,17 @@ Set an endpoint URI option or exchange header with a name 
of `operation` and a v
 |operation |message body |description
 
 |`INDEX_OR_UPDATE` |`String`, `byte[]`, `Reader` or `InputStream` content to 
index or update |Adds or updates content to an index and returns the resulting 
`id` in the message body.
-You can set the name of the target index from the `indexName` URI parameter 
option, or by providing a message header with the key `INDEX_NAME`.
-When updating indexed content, you must provide its id via a message header 
with the key `ID` .
+You can set the name of the target index from the `indexName` URI parameter 
option, or by providing a message header with the key 
`CamelElasticsearchIndexName`.
+When updating indexed content, you must provide its id via a message header 
with the key `CamelElasticsearchId`.
 
 |`GET_BY_ID` |`String` id of content to retrieve |Retrieves a JSON String 
representation of the indexed document, corresponding to the given index id and 
sets it as the message exchange body.
-You can set the name of the target index from the `indexName` URI parameter 
option, or by providing a message header with the key `INDEX_NAME`.
-You must provide the index id of the content to retrieve either in the message 
body, or via a message header with the key `ID` .
+You can set the name of the target index from the `indexName` URI parameter 
option, or by providing a message header with the key 
`CamelElasticsearchIndexName`.
+You must provide the index id of the content to retrieve either via a message 
header with the key `CamelElasticsearchId`, or in the message body: the header 
wins over the body.
+A missing id fails with the error of the server (404).
 
 |`DELETE` |`String` id of content to delete |Deletes the specified `indexName` 
and returns a `boolean` value as the message exchange body, indicating whether 
the operation was successful.
-You can set the name of the target index from the `indexName` URI parameter 
option, or by providing a message header with the key `INDEX_NAME`.
-You must provide the index id of the content to delete either in the message 
body, or via a message header with the key `ID` .
+You can set the name of the target index from the `indexName` URI parameter 
option, or by providing a message header with the key 
`CamelElasticsearchIndexName`.
+You must provide the index id of the content to delete either via a message 
header with the key `CamelElasticsearchId`, or in the message body: the header 
wins over the body.
 
 |`CREATE_INDEX` | |Creates the specified `indexName` and returns a `boolean` 
value as the message exchange body, indicating whether the operation was 
successful.
 You can set the name of the target index to create from the `indexName` URI 
parameter option, or by providing a message header with the key 
`CamelElasticsearchIndexName`.
@@ -67,8 +68,8 @@ You may also provide a header with the key 
`CamelElasticsearchIndexSettings` whe
 |`DELETE_INDEX` | |Deletes the specified `indexName` and returns a `boolean` 
value as the message exchange body, indicating whether the operation was 
successful.
 You can set the name of the target index to create from the `indexName` URI 
parameter option, or by providing a message header with the key 
`CamelElasticsearchIndexName`.
 
-|`SEARCH` |`Map` (optional) |Search for content with either a `Map` of 
`String` keys & values of query criteria in the body, or a JSON string 
representation of the query in the `CamelElasticsearchSearchQuery` header, 
which wins over the body. A JSON string in the body is not used: with no `Map` 
body and no header, all documents match. Matching documents are returned as a 
JSON array string of their sources set on the message exchange body.
-You can set the JSON query String by providing a message header with the key 
`CamelElasticsearchSearchQuery`.
+|`SEARCH` |`Map` or JSON `String` (optional) |Search for content with either a 
`Map` of `String` keys & values of query criteria in the body, a JSON string 
representation of the query in the body, or one in the 
`CamelElasticsearchSearchQuery` header, which wins over the body. With no body 
and no header, all documents match. Matching documents are returned as a JSON 
array string of their sources set on the message exchange body.
+You can set the JSON query String by providing a message header with the key 
`CamelElasticsearchSearchQuery`, or as the message body.
 You can set the message exchange body to a `Map` of `String` keys & values for 
the query criteria.
 
 |===
@@ -121,7 +122,7 @@ YAML::
 ----
 ====
 
-To update existing indexed content, provide the `ID` message header and the 
message body with the updated content.
+To update existing indexed content, provide the `CamelElasticsearchId` message 
header and the message body with the updated content.
 
 [tabs]
 ====
@@ -219,7 +220,7 @@ YAML::
 
 === Delete Example
 
-To delete indexed content, provide the `ID` message header.
+To delete indexed content, provide the `CamelElasticsearchId` message header.
 
 [tabs]
 ====
@@ -407,6 +408,16 @@ YAML::
 ----
 ====
 
+The JSON query can also be the message body, when the 
`CamelElasticsearchSearchQuery` header is not set.
+
+._Java-only: ProducerTemplate with the JSON query as the body_
+[source,java]
+----
+String query = "{\"query\":{\"match\":{\"content\":\"ElasticSearch With 
Camel\"}}}";
+String hits = 
template.requestBody("elasticsearch-rest-client://myCluster?operation=SEARCH&indexName=myIndex",
 query,
+        String.class);
+----
+
 Search on specific field(s) using `Map`.
 
 ._Java-only: creating a search criteria Map_
diff --git 
a/components/camel-elasticsearch-rest-client/src/generated/resources/META-INF/org/apache/camel/component/elasticsearch/rest/client/elasticsearch-rest-client.json
 
b/components/camel-elasticsearch-rest-client/src/generated/resources/META-INF/org/apache/camel/component/elasticsearch/rest/client/elasticsearch-rest-client.json
index c5f30ddb187a..2ee6f4690977 100644
--- 
a/components/camel-elasticsearch-rest-client/src/generated/resources/META-INF/org/apache/camel/component/elasticsearch/rest/client/elasticsearch-rest-client.json
+++ 
b/components/camel-elasticsearch-rest-client/src/generated/resources/META-INF/org/apache/camel/component/elasticsearch/rest/client/elasticsearch-rest-client.json
@@ -47,7 +47,7 @@
     "CamelElasticsearchOperation": { "index": 4, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": 
"org.apache.camel.component.elasticsearch.rest.client.ElasticsearchRestClientOperation",
 "enum": [ "INDEX_OR_UPDATE", "GET_BY_ID", "DELETE", "CREATE_INDEX", 
"DELETE_INDEX", "SEARCH" ], "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The operation to perform", 
"constantName": "org.apache.came [...]
   },
   "bodyTypes": {
-    "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "value", 
"map", "null" ], "byOption": [ { "option": "operation", "value": 
"INDEX_OR_UPDATE", "carriers": [ "text", "bytes", "stream", "file" ] }, { 
"option": "operation", "value": "GET_BY_ID", "carriers": [ "text", "value", 
"null" ] }, { "option": "operation", "value": "DELETE", "carriers": [ "text", 
"value", "null" ] }, { "option": "operation", "value": "SEARCH", "carriers": [ 
"map", "null" ] }, { "option": "operation" [...]
+    "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "value", 
"map", "null" ], "byOption": [ { "option": "operation", "value": 
"INDEX_OR_UPDATE", "carriers": [ "text", "bytes", "stream", "file" ] }, { 
"option": "operation", "value": "GET_BY_ID", "carriers": [ "text", "value", 
"null" ] }, { "option": "operation", "value": "DELETE", "carriers": [ "text", 
"value", "null" ] }, { "option": "operation", "value": "SEARCH", "carriers": [ 
"text", "bytes", "stream", "file", "map",  [...]
     "producerOut": { "carriers": [ "text", "value" ], "byOption": [ { 
"option": "operation", "value": "INDEX_OR_UPDATE", "carriers": [ "text" ], 
"javaType": "java.lang.String" }, { "option": "operation", "value": 
"GET_BY_ID", "carriers": [ "text" ], "javaType": "java.lang.String" }, { 
"option": "operation", "value": "SEARCH", "carriers": [ "text" ], "javaType": 
"java.lang.String" }, { "option": "operation", "value": "DELETE", "carriers": [ 
"value" ], "javaType": "java.lang.Boolean" }, {  [...]
   },
   "properties": {
diff --git 
a/components/camel-elasticsearch-rest-client/src/main/docs/elasticsearch-rest-client-component.adoc
 
b/components/camel-elasticsearch-rest-client/src/main/docs/elasticsearch-rest-client-component.adoc
index 50d794edd785..2f920a8bce15 100644
--- 
a/components/camel-elasticsearch-rest-client/src/main/docs/elasticsearch-rest-client-component.adoc
+++ 
b/components/camel-elasticsearch-rest-client/src/main/docs/elasticsearch-rest-client-component.adoc
@@ -49,16 +49,17 @@ Set an endpoint URI option or exchange header with a name 
of `operation` and a v
 |operation |message body |description
 
 |`INDEX_OR_UPDATE` |`String`, `byte[]`, `Reader` or `InputStream` content to 
index or update |Adds or updates content to an index and returns the resulting 
`id` in the message body.
-You can set the name of the target index from the `indexName` URI parameter 
option, or by providing a message header with the key `INDEX_NAME`.
-When updating indexed content, you must provide its id via a message header 
with the key `ID` .
+You can set the name of the target index from the `indexName` URI parameter 
option, or by providing a message header with the key 
`CamelElasticsearchIndexName`.
+When updating indexed content, you must provide its id via a message header 
with the key `CamelElasticsearchId`.
 
 |`GET_BY_ID` |`String` id of content to retrieve |Retrieves a JSON String 
representation of the indexed document, corresponding to the given index id and 
sets it as the message exchange body.
-You can set the name of the target index from the `indexName` URI parameter 
option, or by providing a message header with the key `INDEX_NAME`.
-You must provide the index id of the content to retrieve either in the message 
body, or via a message header with the key `ID` .
+You can set the name of the target index from the `indexName` URI parameter 
option, or by providing a message header with the key 
`CamelElasticsearchIndexName`.
+You must provide the index id of the content to retrieve either via a message 
header with the key `CamelElasticsearchId`, or in the message body: the header 
wins over the body.
+A missing id fails with the error of the server (404).
 
 |`DELETE` |`String` id of content to delete |Deletes the specified `indexName` 
and returns a `boolean` value as the message exchange body, indicating whether 
the operation was successful.
-You can set the name of the target index from the `indexName` URI parameter 
option, or by providing a message header with the key `INDEX_NAME`.
-You must provide the index id of the content to delete either in the message 
body, or via a message header with the key `ID` .
+You can set the name of the target index from the `indexName` URI parameter 
option, or by providing a message header with the key 
`CamelElasticsearchIndexName`.
+You must provide the index id of the content to delete either via a message 
header with the key `CamelElasticsearchId`, or in the message body: the header 
wins over the body.
 
 |`CREATE_INDEX` | |Creates the specified `indexName` and returns a `boolean` 
value as the message exchange body, indicating whether the operation was 
successful.
 You can set the name of the target index to create from the `indexName` URI 
parameter option, or by providing a message header with the key 
`CamelElasticsearchIndexName`.
@@ -67,8 +68,8 @@ You may also provide a header with the key 
`CamelElasticsearchIndexSettings` whe
 |`DELETE_INDEX` | |Deletes the specified `indexName` and returns a `boolean` 
value as the message exchange body, indicating whether the operation was 
successful.
 You can set the name of the target index to create from the `indexName` URI 
parameter option, or by providing a message header with the key 
`CamelElasticsearchIndexName`.
 
-|`SEARCH` |`Map` (optional) |Search for content with either a `Map` of 
`String` keys & values of query criteria in the body, or a JSON string 
representation of the query in the `CamelElasticsearchSearchQuery` header, 
which wins over the body. A JSON string in the body is not used: with no `Map` 
body and no header, all documents match. Matching documents are returned as a 
JSON array string of their sources set on the message exchange body.
-You can set the JSON query String by providing a message header with the key 
`CamelElasticsearchSearchQuery`.
+|`SEARCH` |`Map` or JSON `String` (optional) |Search for content with either a 
`Map` of `String` keys & values of query criteria in the body, a JSON string 
representation of the query in the body, or one in the 
`CamelElasticsearchSearchQuery` header, which wins over the body. With no body 
and no header, all documents match. Matching documents are returned as a JSON 
array string of their sources set on the message exchange body.
+You can set the JSON query String by providing a message header with the key 
`CamelElasticsearchSearchQuery`, or as the message body.
 You can set the message exchange body to a `Map` of `String` keys & values for 
the query criteria.
 
 |===
@@ -121,7 +122,7 @@ YAML::
 ----
 ====
 
-To update existing indexed content, provide the `ID` message header and the 
message body with the updated content.
+To update existing indexed content, provide the `CamelElasticsearchId` message 
header and the message body with the updated content.
 
 [tabs]
 ====
@@ -219,7 +220,7 @@ YAML::
 
 === Delete Example
 
-To delete indexed content, provide the `ID` message header.
+To delete indexed content, provide the `CamelElasticsearchId` message header.
 
 [tabs]
 ====
@@ -407,6 +408,16 @@ YAML::
 ----
 ====
 
+The JSON query can also be the message body, when the 
`CamelElasticsearchSearchQuery` header is not set.
+
+._Java-only: ProducerTemplate with the JSON query as the body_
+[source,java]
+----
+String query = "{\"query\":{\"match\":{\"content\":\"ElasticSearch With 
Camel\"}}}";
+String hits = 
template.requestBody("elasticsearch-rest-client://myCluster?operation=SEARCH&indexName=myIndex",
 query,
+        String.class);
+----
+
 Search on specific field(s) using `Map`.
 
 ._Java-only: creating a search criteria Map_
diff --git 
a/components/camel-elasticsearch-rest-client/src/main/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientEndpoint.java
 
b/components/camel-elasticsearch-rest-client/src/main/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientEndpoint.java
index 1a839cb9791a..9d1fc4125539 100644
--- 
a/components/camel-elasticsearch-rest-client/src/main/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientEndpoint.java
+++ 
b/components/camel-elasticsearch-rest-client/src/main/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientEndpoint.java
@@ -50,18 +50,20 @@ import org.elasticsearch.client.RestClient;
                                           @BodyType.When(option = "operation", 
value = "DELETE",
                                                          carriers = { 
Carrier.TEXT, Carrier.VALUE, Carrier.NULL }),
                                           @BodyType.When(option = "operation", 
value = "SEARCH",
-                                                         carriers = { 
Carrier.MAP, Carrier.NULL }),
+                                                         carriers = {
+                                                                 Carrier.TEXT, 
Carrier.BYTES, Carrier.STREAM, Carrier.FILE,
+                                                                 Carrier.MAP, 
Carrier.NULL }),
                                           @BodyType.When(option = "operation", 
value = "CREATE_INDEX", carriers = Carrier.ANY),
                                           @BodyType.When(option = "operation", 
value = "DELETE_INDEX",
                                                          carriers = 
Carrier.ANY) },
                                   description = "By the operation, set by the 
operation option or the CamelElasticsearchOperation"
                                                 + " header. INDEX_OR_UPDATE 
sends the body as the JSON document: text, bytes, a"
                                                 + " stream or a file of it; a 
Map or a POJO is sent as its toString() and"
-                                                + " fails, so marshal it to 
JSON first. GET_BY_ID and DELETE take the id as"
-                                                + " the body, or from the 
CamelElasticsearchId header when the body is empty."
-                                                + " SEARCH takes a Map of 
field names and values to match, or else the JSON"
-                                                + " query of the 
CamelElasticsearchSearchQuery header; a JSON text body is"
-                                                + " not used, and with neither 
all documents match. CREATE_INDEX and"
+                                                + " fails, so marshal it to 
JSON first. GET_BY_ID and DELETE take the id of"
+                                                + " the CamelElasticsearchId 
header, or else the body. SEARCH takes the JSON"
+                                                + " query of the 
CamelElasticsearchSearchQuery header, or else the body: a Map"
+                                                + " of field names and values 
to match, or the JSON query as text, bytes, a"
+                                                + " stream or a file; with no 
body all documents match. CREATE_INDEX and"
                                                 + " DELETE_INDEX do not use 
the body: the settings of a new index are in the"
                                                 + " 
CamelElasticsearchIndexSettings header"),
            producerOut = @BodyType(value = { Carrier.TEXT, Carrier.VALUE },
diff --git 
a/components/camel-elasticsearch-rest-client/src/main/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientProducer.java
 
b/components/camel-elasticsearch-rest-client/src/main/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientProducer.java
index 401e78593f90..3f20a5b369f5 100644
--- 
a/components/camel-elasticsearch-rest-client/src/main/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientProducer.java
+++ 
b/components/camel-elasticsearch-rest-client/src/main/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientProducer.java
@@ -247,9 +247,10 @@ public class ElasticsearchRestClientProducer extends 
DefaultAsyncProducer {
      * Generate the Request for operation GET_BY_ID
      */
     private Request getById(String indexName, Exchange exchange) {
-        var id = exchange.getMessage().getBody(String.class);
+        // the id of the header, as for the other operations, else the one of 
the body
+        var id = 
exchange.getMessage().getHeader(ElasticSearchRestClientConstant.ID, 
String.class);
         if (ObjectHelper.isEmpty(id)) {
-            id = 
exchange.getMessage().getHeader(ElasticSearchRestClientConstant.ID, 
String.class);
+            id = exchange.getMessage().getBody(String.class);
             if (ObjectHelper.isEmpty(id)) {
                 throw new IllegalArgumentException(
                         "id value is mandatory when performing GET_BY_ID 
operation");
@@ -263,9 +264,10 @@ public class ElasticsearchRestClientProducer extends 
DefaultAsyncProducer {
      * Generate the Request for DELETE
      */
     private Request delete(String indexName, Exchange exchange) {
-        var id = exchange.getMessage().getBody(String.class);
+        // the id of the header, as for the other operations, else the one of 
the body
+        var id = 
exchange.getMessage().getHeader(ElasticSearchRestClientConstant.ID, 
String.class);
         if (ObjectHelper.isEmpty(id)) {
-            id = 
exchange.getMessage().getHeader(ElasticSearchRestClientConstant.ID, 
String.class);
+            id = exchange.getMessage().getBody(String.class);
             if (ObjectHelper.isEmpty(id)) {
                 throw new IllegalArgumentException(
                         "id value is mandatory when performing DELETE 
operation");
@@ -293,6 +295,12 @@ public class ElasticsearchRestClientProducer extends 
DefaultAsyncProducer {
             if (ObjectHelper.isNotEmpty(queryParameters)) {
                 var jsonRequest = createQueryFromMap(queryParameters);
                 request.setJsonEntity(jsonRequest);
+            } else if (queryParameters == null) {
+                // else the body is the JSON query, as the header would be
+                var jsonQuery = exchange.getMessage().getBody(String.class);
+                if (ObjectHelper.isNotEmpty(jsonQuery)) {
+                    request.setJsonEntity(jsonQuery);
+                }
             }
         }
 
@@ -347,12 +355,7 @@ public class ElasticsearchRestClientProducer extends 
DefaultAsyncProducer {
      * Extract Document from response
      */
     String extractDocument(JsonObject doc) {
-        // check if document exists
-        Boolean found = doc.getBoolean("found");
-        if (!found) {
-            return null;
-        }
-        // if it exists extract document
+        // the document exists: for a missing document the server answers 404, 
which fails the request before
         Map<String, Object> map = doc.getMap("_source");
         JsonObject jsonObject = new JsonObject(map);
 
diff --git 
a/components/camel-elasticsearch-rest-client/src/test/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientProducerBodyTest.java
 
b/components/camel-elasticsearch-rest-client/src/test/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientProducerBodyTest.java
new file mode 100644
index 000000000000..bfa0d3f6f8d5
--- /dev/null
+++ 
b/components/camel-elasticsearch-rest-client/src/test/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientProducerBodyTest.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.elasticsearch.rest.client;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpServer;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.elasticsearch.client.ResponseException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+/**
+ * The request the producer sends for the bodies and headers it takes, checked 
against a node that records the requests
+ * and answers them as Elasticsearch does.
+ */
+class ElasticsearchRestClientProducerBodyTest {
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+    private static final String QUERY = 
"{\"query\":{\"match\":{\"title\":\"camel\"}}}";
+
+    private final List<Recorded> requests = new CopyOnWriteArrayList<>();
+    private HttpServer node;
+    private CamelContext context;
+    private ProducerTemplate template;
+    private String uri;
+
+    @BeforeEach
+    void start() throws Exception {
+        node = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
+        node.createContext("/", this::answer);
+        node.start();
+        uri = 
"elasticsearch-rest-client:camel?indexName=my_index&hostAddressesList=127.0.0.1:"
+              + node.getAddress().getPort();
+        context = new DefaultCamelContext();
+        context.start();
+        template = context.createProducerTemplate();
+    }
+
+    @AfterEach
+    void stop() {
+        context.stop();
+        node.stop(0);
+    }
+
+    @Test
+    void searchSendsAJsonBodyAsTheQuery() throws Exception {
+        assertEquals("[{\"title\":\"camel\"}]", template.requestBody(uri + 
"&operation=SEARCH", QUERY, String.class));
+
+        assertEquals("/my_index/_search", single().path());
+        assertEquals(json(QUERY), json(single().body()));
+    }
+
+    @Test
+    void searchSendsAJsonStreamBodyAsTheQuery() throws Exception {
+        template.requestBody(uri + "&operation=SEARCH", new 
ByteArrayInputStream(QUERY.getBytes(StandardCharsets.UTF_8)),
+                String.class);
+
+        assertEquals(json(QUERY), json(single().body()));
+    }
+
+    @Test
+    void searchPrefersTheQueryHeaderToTheBody() throws Exception {
+        String header = "{\"query\":{\"match_all\":{}}}";
+
+        template.requestBodyAndHeader(uri + "&operation=SEARCH", QUERY, 
ElasticSearchRestClientConstant.SEARCH_QUERY, header,
+                String.class);
+
+        assertEquals(json(header), json(single().body()));
+    }
+
+    @Test
+    void searchStillMatchesTheFieldsOfAMapBody() throws Exception {
+        template.requestBody(uri + "&operation=SEARCH", Map.of("title", 
"camel"), String.class);
+
+        
assertEquals(json("{\"query\":{\"bool\":{\"must\":[{\"match\":{\"title\":\"camel\"}}]}}}"),
 json(single().body()));
+    }
+
+    @Test
+    void searchWithoutBodyStillMatchesEveryDocument() {
+        template.requestBody(uri + "&operation=SEARCH", null, String.class);
+
+        assertEquals("", single().body());
+    }
+
+    @Test
+    void getByIdPrefersTheIdHeaderToALeftoverBody() {
+        String document = template.requestBodyAndHeader(uri + 
"&operation=GET_BY_ID", "leftover",
+                ElasticSearchRestClientConstant.ID, "1", String.class);
+
+        assertEquals("{\"title\":\"camel\"}", document);
+        assertEquals("GET", single().method());
+        assertEquals("/my_index/_doc/1", single().path());
+    }
+
+    @Test
+    void getByIdStillTakesTheIdOfTheBodyWithoutHeader() {
+        template.requestBody(uri + "&operation=GET_BY_ID", "1", String.class);
+
+        assertEquals("/my_index/_doc/1", single().path());
+    }
+
+    @Test
+    void getByIdOfAMissingDocumentFailsWithTheErrorOfTheServer() {
+        Exchange exchange = template.request(uri + "&operation=GET_BY_ID",
+                e -> 
e.getMessage().setHeader(ElasticSearchRestClientConstant.ID, "missing"));
+
+        ResponseException e = assertInstanceOf(ResponseException.class, 
exchange.getException());
+        assertEquals(404, e.getResponse().getStatusLine().getStatusCode());
+    }
+
+    @Test
+    void deletePrefersTheIdHeaderToALeftoverBody() {
+        Object deleted = template.requestBodyAndHeader(uri + 
"&operation=DELETE", "leftover",
+                ElasticSearchRestClientConstant.ID, "1");
+
+        assertEquals(Boolean.TRUE, deleted);
+        assertEquals("DELETE", single().method());
+        assertEquals("/my_index/_doc/1", single().path());
+    }
+
+    private Recorded single() {
+        assertEquals(1, requests.size(), "requests sent: " + requests);
+        return requests.get(0);
+    }
+
+    private static JsonNode json(String text) throws IOException {
+        return MAPPER.readTree(text);
+    }
+
+    private void answer(HttpExchange exchange) throws IOException {
+        String body = new String(exchange.getRequestBody().readAllBytes(), 
StandardCharsets.UTF_8);
+        String method = exchange.getRequestMethod();
+        String path = exchange.getRequestURI().getPath();
+        requests.add(new Recorded(method, path, body));
+        int status = 200;
+        String response;
+        if (path.endsWith("/_search")) {
+            response = 
"{\"hits\":{\"hits\":[{\"_source\":{\"title\":\"camel\"}}]}}";
+        } else if (path.endsWith("/_doc/missing")) {
+            status = 404;
+            response = 
"{\"_index\":\"my_index\",\"_id\":\"missing\",\"found\":false}";
+        } else if ("DELETE".equals(method)) {
+            response = 
"{\"_index\":\"my_index\",\"_id\":\"1\",\"result\":\"deleted\"}";
+        } else {
+            response = 
"{\"_index\":\"my_index\",\"_id\":\"1\",\"found\":true,\"_source\":{\"title\":\"camel\"}}";
+        }
+        byte[] bytes = response.getBytes(StandardCharsets.UTF_8);
+        exchange.getResponseHeaders().add("Content-Type", "application/json");
+        exchange.sendResponseHeaders(status, bytes.length);
+        try (OutputStream out = exchange.getResponseBody()) {
+            out.write(bytes);
+        }
+    }
+
+    private record Recorded(String method, String path, String body) {
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 707cfc139aa9..a03f79d36c49 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -4858,6 +4858,17 @@ In token mode a `SlackMessage` body now sends its 
attachments too, not only its
 The Slack CloudEvents data type transformer now sets `CamelCloudEventSource` 
to `slack.<appId>` and
 `CamelCloudEventSubject` to the channel. Previously they were `slack.true` and 
`true`.
 
+=== camel-elasticsearch-rest-client - SEARCH body and the id of GET_BY_ID and 
DELETE
+
+The `SEARCH` operation now sends a body that is not a `Map` (JSON text, bytes, 
a stream or a file) as the JSON query,
+when the `CamelElasticsearchSearchQuery` header is not set. Before, such a 
body was ignored and every document matched.
+A route whose `SEARCH` step gets a body left by a previous step, such as the 
id given by `INDEX_OR_UPDATE`, now sends
+that body as the query and fails: remove the body, or set the query in the 
header, to match every document as before.
+
+The `GET_BY_ID` and `DELETE` operations now take the id of the 
`CamelElasticsearchId` header first, and the body only
+when the header is not set, as `INDEX_OR_UPDATE` does. Before, the body won 
over the header, so a body left by a
+previous step was used as the id.
+
 === camel-telegram - media bodies, messages without text, and answers that are 
not ok
 
 The `telegram` producer changes:

Reply via email to