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 7b14a24bcb4b CAMEL-26178: camel-coap - a body that does not convert to 
a byte array fails instead of being sent as an empty payload (#29004)
7b14a24bcb4b is described below

commit 7b14a24bcb4b45aad92d51832b8467a880e760eb
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Nov 3 07:39:05 2026 +0100

    CAMEL-26178: camel-coap - a body that does not convert to a byte array 
fails instead of being sent as an empty payload (#29004)
    
    …y fails instead of being sent as an empty payload
    
    The producer read the POST and PUT payload with getBody(byte[].class), so a 
Map, a POJO
    or a number was sent as an empty payload without an error. It now uses 
getMandatoryBody
    and fails the exchange with an InvalidPayloadException; no body still sends 
an empty payload.
    
    The consumer answered such a reply body with 2.05 Content and an empty 
payload. It now
    uses getMandatoryBody for a non-null reply body, so the existing catch 
answers 5.00 as
    the component page documents. The body types, the component page and the 
upgrade guide
    are updated.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../apache/camel/catalog/components/coap+tcp.json  |   4 +-
 .../org/apache/camel/catalog/components/coap.json  |   4 +-
 .../apache/camel/catalog/components/coaps+tcp.json |   4 +-
 .../org/apache/camel/catalog/components/coaps.json |   4 +-
 .../apache/camel/catalog/docs/coap-component.adoc  |   7 +-
 .../META-INF/org/apache/camel/coap/coap+tcp.json   |   4 +-
 .../META-INF/org/apache/camel/coap/coap.json       |   4 +-
 .../META-INF/org/apache/camel/coap/coaps+tcp.json  |   4 +-
 .../META-INF/org/apache/camel/coap/coaps.json      |   4 +-
 .../camel-coap/src/main/docs/coap-component.adoc   |   7 +-
 .../org/apache/camel/coap/CamelCoapResource.java   |   4 +-
 .../java/org/apache/camel/coap/CoAPEndpoint.java   |   6 +-
 .../java/org/apache/camel/coap/CoAPProducer.java   |  16 ++-
 .../camel/coap/CoAPUnconvertibleBodyTest.java      | 128 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  12 ++
 15 files changed, 186 insertions(+), 26 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coap+tcp.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coap+tcp.json
index 9a6265a5f139..bcdcea36d561 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coap+tcp.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coap+tcp.json
@@ -41,8 +41,8 @@
   },
   "bodyTypes": {
     "consumerOut": { "carriers": [ "bytes" ], "javaType": "byte[]", 
"description": "The request payload as a byte array, empty for a request 
without a payload, such as a GET; with observe, the payload of each 
notification of the observed resource" },
-    "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file" 
], "description": "The response payload: text, bytes, a stream, a file or no 
body. A Map, a POJO or a number is sent as an empty payload: convert or marshal 
it first" },
-    "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], 
"description": "The payload of a POST or a PUT: text, bytes, a stream or a 
file. A Map, a POJO or a number is sent as an empty payload: marshal it first. 
Without CamelCoapMethod, no body sends a GET and a body a POST" },
+    "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file" 
], "description": "The response payload: text, bytes, a stream, a file or no 
body. A Map, a POJO or a number is answered with 5.00 Internal Server Error: 
convert or marshal it first" },
+    "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], 
"description": "The payload of a POST or a PUT: text, bytes, a stream or a 
file. A Map, a POJO or a number fails the exchange: marshal it first. Without 
CamelCoapMethod, no body sends a GET and a body a POST" },
     "producerOut": { "carriers": [ "bytes", "value" ], "byOption": [ { 
"option": "notify", "value": "true", "carriers": [ "unchanged" ] } ], 
"description": "The response payload as a byte array, empty for a response 
without a payload; a Boolean for a PING (CamelCoapMethod=PING). The response 
code is in the CamelCoapResponseCode header" }
   },
   "properties": {
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coap.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coap.json
index 88c4c89416d9..50d97d226bf3 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coap.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coap.json
@@ -41,8 +41,8 @@
   },
   "bodyTypes": {
     "consumerOut": { "carriers": [ "bytes" ], "javaType": "byte[]", 
"description": "The request payload as a byte array, empty for a request 
without a payload, such as a GET; with observe, the payload of each 
notification of the observed resource" },
-    "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file" 
], "description": "The response payload: text, bytes, a stream, a file or no 
body. A Map, a POJO or a number is sent as an empty payload: convert or marshal 
it first" },
-    "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], 
"description": "The payload of a POST or a PUT: text, bytes, a stream or a 
file. A Map, a POJO or a number is sent as an empty payload: marshal it first. 
Without CamelCoapMethod, no body sends a GET and a body a POST" },
+    "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file" 
], "description": "The response payload: text, bytes, a stream, a file or no 
body. A Map, a POJO or a number is answered with 5.00 Internal Server Error: 
convert or marshal it first" },
+    "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], 
"description": "The payload of a POST or a PUT: text, bytes, a stream or a 
file. A Map, a POJO or a number fails the exchange: marshal it first. Without 
CamelCoapMethod, no body sends a GET and a body a POST" },
     "producerOut": { "carriers": [ "bytes", "value" ], "byOption": [ { 
"option": "notify", "value": "true", "carriers": [ "unchanged" ] } ], 
"description": "The response payload as a byte array, empty for a response 
without a payload; a Boolean for a PING (CamelCoapMethod=PING). The response 
code is in the CamelCoapResponseCode header" }
   },
   "properties": {
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coaps+tcp.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coaps+tcp.json
index f69848818b02..50d31693853d 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coaps+tcp.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coaps+tcp.json
@@ -41,8 +41,8 @@
   },
   "bodyTypes": {
     "consumerOut": { "carriers": [ "bytes" ], "javaType": "byte[]", 
"description": "The request payload as a byte array, empty for a request 
without a payload, such as a GET; with observe, the payload of each 
notification of the observed resource" },
-    "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file" 
], "description": "The response payload: text, bytes, a stream, a file or no 
body. A Map, a POJO or a number is sent as an empty payload: convert or marshal 
it first" },
-    "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], 
"description": "The payload of a POST or a PUT: text, bytes, a stream or a 
file. A Map, a POJO or a number is sent as an empty payload: marshal it first. 
Without CamelCoapMethod, no body sends a GET and a body a POST" },
+    "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file" 
], "description": "The response payload: text, bytes, a stream, a file or no 
body. A Map, a POJO or a number is answered with 5.00 Internal Server Error: 
convert or marshal it first" },
+    "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], 
"description": "The payload of a POST or a PUT: text, bytes, a stream or a 
file. A Map, a POJO or a number fails the exchange: marshal it first. Without 
CamelCoapMethod, no body sends a GET and a body a POST" },
     "producerOut": { "carriers": [ "bytes", "value" ], "byOption": [ { 
"option": "notify", "value": "true", "carriers": [ "unchanged" ] } ], 
"description": "The response payload as a byte array, empty for a response 
without a payload; a Boolean for a PING (CamelCoapMethod=PING). The response 
code is in the CamelCoapResponseCode header" }
   },
   "properties": {
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coaps.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coaps.json
index be9883c933d2..d980f140175d 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coaps.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/coaps.json
@@ -41,8 +41,8 @@
   },
   "bodyTypes": {
     "consumerOut": { "carriers": [ "bytes" ], "javaType": "byte[]", 
"description": "The request payload as a byte array, empty for a request 
without a payload, such as a GET; with observe, the payload of each 
notification of the observed resource" },
-    "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file" 
], "description": "The response payload: text, bytes, a stream, a file or no 
body. A Map, a POJO or a number is sent as an empty payload: convert or marshal 
it first" },
-    "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], 
"description": "The payload of a POST or a PUT: text, bytes, a stream or a 
file. A Map, a POJO or a number is sent as an empty payload: marshal it first. 
Without CamelCoapMethod, no body sends a GET and a body a POST" },
+    "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file" 
], "description": "The response payload: text, bytes, a stream, a file or no 
body. A Map, a POJO or a number is answered with 5.00 Internal Server Error: 
convert or marshal it first" },
+    "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], 
"description": "The payload of a POST or a PUT: text, bytes, a stream or a 
file. A Map, a POJO or a number fails the exchange: marshal it first. Without 
CamelCoapMethod, no body sends a GET and a body a POST" },
     "producerOut": { "carriers": [ "bytes", "value" ], "byOption": [ { 
"option": "notify", "value": "true", "carriers": [ "unchanged" ] } ], 
"description": "The response payload as a byte array, empty for a response 
without a payload; a Boolean for a PING (CamelCoapMethod=PING). The response 
code is in the CamelCoapResponseCode header" }
   },
   "properties": {
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/coap-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/coap-component.adoc
index 25449f89826d..1362c3f1beee 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/coap-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/coap-component.adoc
@@ -66,11 +66,16 @@ The following rules determine which request method the CoAP 
producer will use to
  3. `POST` if the message exchange body is not null.
  4. `GET` otherwise.
 
+The payload of a `POST` or a `PUT` is the message body converted to `byte[]`, 
and a message without a body sends an
+empty payload. A body that cannot be converted, such as a `Map`, a POJO or a 
number, fails the exchange with an
+`InvalidPayloadException`: marshal it first (for example to JSON) or convert 
it to a `String`.
+
 === Error responses from the CoAP consumer
 
 When the exchange fails, the CoAP consumer answers the request with `5.00 
Internal Server Error`. The same applies
 when the response cannot be built once the route has completed, for example 
when the body cannot be converted to
-the response payload.
+the response payload, such as a `Map`, a POJO or a number. A route without a 
reply body answers `2.05 Content` with an
+empty payload.
 
 By default the error response has no payload, so the exception's message is 
not sent to the client. Set
 `muteException=false` on the endpoint to send the exception's message as the 
payload instead.
diff --git 
a/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coap+tcp.json
 
b/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coap+tcp.json
index 9a6265a5f139..bcdcea36d561 100644
--- 
a/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coap+tcp.json
+++ 
b/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coap+tcp.json
@@ -41,8 +41,8 @@
   },
   "bodyTypes": {
     "consumerOut": { "carriers": [ "bytes" ], "javaType": "byte[]", 
"description": "The request payload as a byte array, empty for a request 
without a payload, such as a GET; with observe, the payload of each 
notification of the observed resource" },
-    "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file" 
], "description": "The response payload: text, bytes, a stream, a file or no 
body. A Map, a POJO or a number is sent as an empty payload: convert or marshal 
it first" },
-    "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], 
"description": "The payload of a POST or a PUT: text, bytes, a stream or a 
file. A Map, a POJO or a number is sent as an empty payload: marshal it first. 
Without CamelCoapMethod, no body sends a GET and a body a POST" },
+    "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file" 
], "description": "The response payload: text, bytes, a stream, a file or no 
body. A Map, a POJO or a number is answered with 5.00 Internal Server Error: 
convert or marshal it first" },
+    "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], 
"description": "The payload of a POST or a PUT: text, bytes, a stream or a 
file. A Map, a POJO or a number fails the exchange: marshal it first. Without 
CamelCoapMethod, no body sends a GET and a body a POST" },
     "producerOut": { "carriers": [ "bytes", "value" ], "byOption": [ { 
"option": "notify", "value": "true", "carriers": [ "unchanged" ] } ], 
"description": "The response payload as a byte array, empty for a response 
without a payload; a Boolean for a PING (CamelCoapMethod=PING). The response 
code is in the CamelCoapResponseCode header" }
   },
   "properties": {
diff --git 
a/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coap.json
 
b/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coap.json
index 88c4c89416d9..50d97d226bf3 100644
--- 
a/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coap.json
+++ 
b/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coap.json
@@ -41,8 +41,8 @@
   },
   "bodyTypes": {
     "consumerOut": { "carriers": [ "bytes" ], "javaType": "byte[]", 
"description": "The request payload as a byte array, empty for a request 
without a payload, such as a GET; with observe, the payload of each 
notification of the observed resource" },
-    "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file" 
], "description": "The response payload: text, bytes, a stream, a file or no 
body. A Map, a POJO or a number is sent as an empty payload: convert or marshal 
it first" },
-    "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], 
"description": "The payload of a POST or a PUT: text, bytes, a stream or a 
file. A Map, a POJO or a number is sent as an empty payload: marshal it first. 
Without CamelCoapMethod, no body sends a GET and a body a POST" },
+    "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file" 
], "description": "The response payload: text, bytes, a stream, a file or no 
body. A Map, a POJO or a number is answered with 5.00 Internal Server Error: 
convert or marshal it first" },
+    "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], 
"description": "The payload of a POST or a PUT: text, bytes, a stream or a 
file. A Map, a POJO or a number fails the exchange: marshal it first. Without 
CamelCoapMethod, no body sends a GET and a body a POST" },
     "producerOut": { "carriers": [ "bytes", "value" ], "byOption": [ { 
"option": "notify", "value": "true", "carriers": [ "unchanged" ] } ], 
"description": "The response payload as a byte array, empty for a response 
without a payload; a Boolean for a PING (CamelCoapMethod=PING). The response 
code is in the CamelCoapResponseCode header" }
   },
   "properties": {
diff --git 
a/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coaps+tcp.json
 
b/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coaps+tcp.json
index f69848818b02..50d31693853d 100644
--- 
a/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coaps+tcp.json
+++ 
b/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coaps+tcp.json
@@ -41,8 +41,8 @@
   },
   "bodyTypes": {
     "consumerOut": { "carriers": [ "bytes" ], "javaType": "byte[]", 
"description": "The request payload as a byte array, empty for a request 
without a payload, such as a GET; with observe, the payload of each 
notification of the observed resource" },
-    "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file" 
], "description": "The response payload: text, bytes, a stream, a file or no 
body. A Map, a POJO or a number is sent as an empty payload: convert or marshal 
it first" },
-    "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], 
"description": "The payload of a POST or a PUT: text, bytes, a stream or a 
file. A Map, a POJO or a number is sent as an empty payload: marshal it first. 
Without CamelCoapMethod, no body sends a GET and a body a POST" },
+    "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file" 
], "description": "The response payload: text, bytes, a stream, a file or no 
body. A Map, a POJO or a number is answered with 5.00 Internal Server Error: 
convert or marshal it first" },
+    "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], 
"description": "The payload of a POST or a PUT: text, bytes, a stream or a 
file. A Map, a POJO or a number fails the exchange: marshal it first. Without 
CamelCoapMethod, no body sends a GET and a body a POST" },
     "producerOut": { "carriers": [ "bytes", "value" ], "byOption": [ { 
"option": "notify", "value": "true", "carriers": [ "unchanged" ] } ], 
"description": "The response payload as a byte array, empty for a response 
without a payload; a Boolean for a PING (CamelCoapMethod=PING). The response 
code is in the CamelCoapResponseCode header" }
   },
   "properties": {
diff --git 
a/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coaps.json
 
b/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coaps.json
index be9883c933d2..d980f140175d 100644
--- 
a/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coaps.json
+++ 
b/components/camel-coap/src/generated/resources/META-INF/org/apache/camel/coap/coaps.json
@@ -41,8 +41,8 @@
   },
   "bodyTypes": {
     "consumerOut": { "carriers": [ "bytes" ], "javaType": "byte[]", 
"description": "The request payload as a byte array, empty for a request 
without a payload, such as a GET; with observe, the payload of each 
notification of the observed resource" },
-    "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file" 
], "description": "The response payload: text, bytes, a stream, a file or no 
body. A Map, a POJO or a number is sent as an empty payload: convert or marshal 
it first" },
-    "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], 
"description": "The payload of a POST or a PUT: text, bytes, a stream or a 
file. A Map, a POJO or a number is sent as an empty payload: marshal it first. 
Without CamelCoapMethod, no body sends a GET and a body a POST" },
+    "consumerReply": { "carriers": [ "null", "text", "bytes", "stream", "file" 
], "description": "The response payload: text, bytes, a stream, a file or no 
body. A Map, a POJO or a number is answered with 5.00 Internal Server Error: 
convert or marshal it first" },
+    "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], 
"description": "The payload of a POST or a PUT: text, bytes, a stream or a 
file. A Map, a POJO or a number fails the exchange: marshal it first. Without 
CamelCoapMethod, no body sends a GET and a body a POST" },
     "producerOut": { "carriers": [ "bytes", "value" ], "byOption": [ { 
"option": "notify", "value": "true", "carriers": [ "unchanged" ] } ], 
"description": "The response payload as a byte array, empty for a response 
without a payload; a Boolean for a PING (CamelCoapMethod=PING). The response 
code is in the CamelCoapResponseCode header" }
   },
   "properties": {
diff --git a/components/camel-coap/src/main/docs/coap-component.adoc 
b/components/camel-coap/src/main/docs/coap-component.adoc
index 25449f89826d..1362c3f1beee 100644
--- a/components/camel-coap/src/main/docs/coap-component.adoc
+++ b/components/camel-coap/src/main/docs/coap-component.adoc
@@ -66,11 +66,16 @@ The following rules determine which request method the CoAP 
producer will use to
  3. `POST` if the message exchange body is not null.
  4. `GET` otherwise.
 
+The payload of a `POST` or a `PUT` is the message body converted to `byte[]`, 
and a message without a body sends an
+empty payload. A body that cannot be converted, such as a `Map`, a POJO or a 
number, fails the exchange with an
+`InvalidPayloadException`: marshal it first (for example to JSON) or convert 
it to a `String`.
+
 === Error responses from the CoAP consumer
 
 When the exchange fails, the CoAP consumer answers the request with `5.00 
Internal Server Error`. The same applies
 when the response cannot be built once the route has completed, for example 
when the body cannot be converted to
-the response payload.
+the response payload, such as a `Map`, a POJO or a number. A route without a 
reply body answers `2.05 Content` with an
+empty payload.
 
 By default the error response has no payload, so the exception's message is 
not sent to the client. Set
 `muteException=false` on the endpoint to send the exception's message as the 
payload instead.
diff --git 
a/components/camel-coap/src/main/java/org/apache/camel/coap/CamelCoapResource.java
 
b/components/camel-coap/src/main/java/org/apache/camel/coap/CamelCoapResource.java
index 7366f4c7e257..af7390d37250 100644
--- 
a/components/camel-coap/src/main/java/org/apache/camel/coap/CamelCoapResource.java
+++ 
b/components/camel-coap/src/main/java/org/apache/camel/coap/CamelCoapResource.java
@@ -165,7 +165,9 @@ final class CamelCoapResource extends CoapResource {
             }
 
             int format = 
MediaTypeRegistry.parse(target.getHeader(CoAPConstants.CONTENT_TYPE, 
String.class));
-            cexchange.respond(ResponseCode.CONTENT, 
target.getBody(byte[].class), format);
+            // a body that cannot be converted fails the response with 5.00, 
instead of being sent as an empty payload
+            byte[] payload = target.getBody() != null ? 
target.getMandatoryBody(byte[].class) : null;
+            cexchange.respond(ResponseCode.CONTENT, payload, format);
 
         } catch (Exception e) {
             if (consumer != null) {
diff --git 
a/components/camel-coap/src/main/java/org/apache/camel/coap/CoAPEndpoint.java 
b/components/camel-coap/src/main/java/org/apache/camel/coap/CoAPEndpoint.java
index e94645a93ace..7a2bf2e1c3db 100644
--- 
a/components/camel-coap/src/main/java/org/apache/camel/coap/CoAPEndpoint.java
+++ 
b/components/camel-coap/src/main/java/org/apache/camel/coap/CoAPEndpoint.java
@@ -89,11 +89,11 @@ import static 
org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDE
                                                  + " notification of the 
observed resource"),
            consumerReply = @BodyType(value = { Carrier.NULL, Carrier.TEXT, 
Carrier.BYTES, Carrier.STREAM, Carrier.FILE },
                                      description = "The response payload: 
text, bytes, a stream, a file or no body. A Map,"
-                                                   + " a POJO or a number is 
sent as an empty payload: convert or marshal"
-                                                   + " it first"),
+                                                   + " a POJO or a number is 
answered with 5.00 Internal Server Error:"
+                                                   + " convert or marshal it 
first"),
            producerIn = @BodyType(value = { Carrier.NULL, Carrier.TEXT, 
Carrier.BYTES, Carrier.STREAM, Carrier.FILE },
                                   description = "The payload of a POST or a 
PUT: text, bytes, a stream or a file. A Map,"
-                                                + " a POJO or a number is sent 
as an empty payload: marshal it first."
+                                                + " a POJO or a number fails 
the exchange: marshal it first."
                                                 + " Without CamelCoapMethod, 
no body sends a GET and a body a POST"),
            producerOut = @BodyType(value = { Carrier.BYTES, Carrier.VALUE },
                                    byOption = @BodyType.When(option = 
"notify", value = "true",
diff --git 
a/components/camel-coap/src/main/java/org/apache/camel/coap/CoAPProducer.java 
b/components/camel-coap/src/main/java/org/apache/camel/coap/CoAPProducer.java
index e539607eaf44..adc57e04e092 100644
--- 
a/components/camel-coap/src/main/java/org/apache/camel/coap/CoAPProducer.java
+++ 
b/components/camel-coap/src/main/java/org/apache/camel/coap/CoAPProducer.java
@@ -20,6 +20,7 @@ import java.util.Locale;
 
 import org.apache.camel.CamelExchangeException;
 import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
 import org.apache.camel.Message;
 import org.apache.camel.support.DefaultProducer;
 import org.apache.camel.util.URISupport;
@@ -82,12 +83,10 @@ public class CoAPProducer extends DefaultProducer {
                 response = coapClient.delete();
                 break;
             case CoAPConstants.METHOD_POST:
-                byte[] bodyPost = exchange.getIn().getBody(byte[].class);
-                response = coapClient.post(bodyPost, mediaType);
+                response = coapClient.post(getPayload(exchange), mediaType);
                 break;
             case CoAPConstants.METHOD_PUT:
-                byte[] bodyPut = exchange.getIn().getBody(byte[].class);
-                response = coapClient.put(bodyPut, mediaType);
+                response = coapClient.put(getPayload(exchange), mediaType);
                 break;
             case CoAPConstants.METHOD_PING:
                 pingResponse = coapClient.ping();
@@ -111,6 +110,15 @@ public class CoAPProducer extends DefaultProducer {
         }
     }
 
+    private static byte[] getPayload(Exchange exchange) throws 
InvalidPayloadException {
+        Message in = exchange.getIn();
+        if (in.getBody() == null) {
+            return null;
+        }
+        // a body that cannot be converted fails the exchange, instead of 
being sent as an empty payload
+        return in.getMandatoryBody(byte[].class);
+    }
+
     protected void initClient() throws Exception {
         lock.lock();
         try {
diff --git 
a/components/camel-coap/src/test/java/org/apache/camel/coap/CoAPUnconvertibleBodyTest.java
 
b/components/camel-coap/src/test/java/org/apache/camel/coap/CoAPUnconvertibleBodyTest.java
new file mode 100644
index 000000000000..6fe3e6846570
--- /dev/null
+++ 
b/components/camel-coap/src/test/java/org/apache/camel/coap/CoAPUnconvertibleBodyTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.coap;
+
+import java.util.Map;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.eclipse.californium.core.CoapResponse;
+import org.eclipse.californium.core.coap.CoAP.ResponseCode;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+/**
+ * A body that cannot be converted to the CoAP payload must fail, instead of 
being sent as an empty payload.
+ */
+public class CoAPUnconvertibleBodyTest extends CoAPTestSupport {
+
+    @Test
+    void producerFailsPostOfABodyThatCannotBeConverted() throws Exception {
+        MockEndpoint sink = getMockEndpoint("mock:sink");
+        sink.expectedMessageCount(0);
+
+        Exchange out = template.request(sinkUri(), e -> {
+            e.getIn().setHeader(CoAPConstants.COAP_METHOD, 
CoAPConstants.METHOD_POST);
+            e.getIn().setBody(Map.of("temperature", 21));
+        });
+
+        assertInstanceOf(InvalidPayloadException.class, out.getException());
+        MockEndpoint.assertIsSatisfied(context);
+    }
+
+    @Test
+    void producerFailsPutOfABodyThatCannotBeConverted() throws Exception {
+        MockEndpoint sink = getMockEndpoint("mock:sink");
+        sink.expectedMessageCount(0);
+
+        Exchange out = template.request(sinkUri(), e -> {
+            e.getIn().setHeader(CoAPConstants.COAP_METHOD, 
CoAPConstants.METHOD_PUT);
+            e.getIn().setBody(new Reading(21));
+        });
+
+        assertInstanceOf(InvalidPayloadException.class, out.getException());
+        MockEndpoint.assertIsSatisfied(context);
+    }
+
+    @Test
+    void producerSendsAnEmptyPayloadForAPostWithoutBody() throws Exception {
+        MockEndpoint sink = getMockEndpoint("mock:sink");
+        sink.expectedMessageCount(1);
+
+        Exchange out = template.request(sinkUri(), e -> 
e.getIn().setHeader(CoAPConstants.COAP_METHOD,
+                CoAPConstants.METHOD_POST));
+
+        assertNull(out.getException());
+        MockEndpoint.assertIsSatisfied(context);
+        assertEquals(0, 
sink.getReceivedExchanges().get(0).getIn().getBody(byte[].class).length);
+    }
+
+    @Test
+    void consumerAnswersAReplyBodyThatCannotBeConvertedWithServerError() 
throws Exception {
+        CoapResponse response = createClient("/mapReply").get();
+
+        assertEquals(ResponseCode.INTERNAL_SERVER_ERROR, response.getCode());
+        assertEquals(0, response.getPayload().length, "the response must not 
carry the exception's message");
+    }
+
+    @Test
+    void consumerAnswersAPojoReplyBodyWithServerError() throws Exception {
+        CoapResponse response = createClient("/pojoReply").get();
+
+        assertEquals(ResponseCode.INTERNAL_SERVER_ERROR, response.getCode());
+    }
+
+    @Test
+    void consumerAnswersANullReplyBodyWithAnEmptyContent() throws Exception {
+        CoapResponse response = createClient("/nullReply").get();
+
+        assertEquals(ResponseCode.CONTENT, response.getCode());
+        assertEquals(0, response.getPayload().length);
+    }
+
+    private String sinkUri() {
+        return "coap://localhost:" + PORT.getPort() + "/sink";
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                fromF("coap://localhost:%d/sink", PORT.getPort())
+                        .to("mock:sink");
+
+                fromF("coap://localhost:%d/mapReply", PORT.getPort())
+                        .process(e -> 
e.getMessage().setBody(Map.of("temperature", 21)));
+
+                fromF("coap://localhost:%d/pojoReply", PORT.getPort())
+                        .process(e -> e.getMessage().setBody(new Reading(21)));
+
+                fromF("coap://localhost:%d/nullReply", PORT.getPort())
+                        .setBody(constant(null));
+            }
+        };
+    }
+
+    private record Reading(int temperature) {
+    }
+}
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 958cee6549cd..da68f957b38c 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
@@ -711,6 +711,18 @@ with `defaultFrom`.
 When the validation of an `SMSMessage` fails, the 
`InvalidPayloadRuntimeException` now names the invalid fields in its
 message, and its cause is an `org.apache.camel.ValidationException`.
 
+=== camel-coap - a body that does not convert to a byte array fails
+
+The `coap` producer sent a `POST` or `PUT` body that has no conversion to 
`byte[]`, such as a `Map`, a POJO or a
+number, as an empty payload, without an error, so the data was lost. It now 
fails the exchange with an
+`InvalidPayloadException`. A message without a body is still sent with an 
empty payload.
+
+The `coap` consumer answered such a reply body with `2.05 Content` and an 
empty payload. It now answers
+`5.00 Internal Server Error`, as the CoAP component page documents. A route 
without a reply body still answers
+`2.05 Content` with an empty payload.
+
+Marshal such a body first (for example to JSON) or convert it to a `String`.
+
 === camel-aws - the HTTP client is no longer looked up on the classpath
 
 The camel-aws components now set the HTTP client of the AWS SDK clients they 
create for their endpoints: the Apache 5

Reply via email to