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

davsclaus pushed a commit to branch CAMEL-26146-jooq-operation-empty-body
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 8a51ef6c519690d359e769680cbef86aa4903c2b
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 2 17:25:27 2026 +0100

    CAMEL-26146: camel-jooq - fail on an empty body and drop the CONSUME 
operation from the catalog
    
    The operation option listed CONSUME in its enums, but JooqOperation has no 
such value, so the
    catalog and docs offered a value that fails endpoint creation. It is 
removed.
    
    With a null body the producer created a new instance of the expected type: 
for operation NONE an
    empty record of the entityType, which was then stored, and for EXECUTE and 
FETCH an
    org.jooq.Query or ResultQuery, which are interfaces and failed with 
NoSuchMethodException. The
    producer now fails with an InvalidPayloadException that says what the 
operation needs.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../org/apache/camel/catalog/components/jooq.json  |  4 +-
 .../apache/camel/catalog/docs/jooq-component.adoc  |  3 +
 .../org/apache/camel/component/jooq/jooq.json      |  4 +-
 .../camel-jooq/src/main/docs/jooq-component.adoc   |  3 +
 .../camel/component/jooq/JooqConfiguration.java    |  2 +-
 .../apache/camel/component/jooq/JooqEndpoint.java  |  9 ++-
 .../camel/component/jooq/JooqComponentTest.java    | 12 +++
 .../component/jooq/JooqProducerEmptyBodyTest.java  | 93 ++++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  8 ++
 9 files changed, 132 insertions(+), 6 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/jooq.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/jooq.json
index bce0ae172257..d6c6e38ce898 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/jooq.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/jooq.json
@@ -29,7 +29,7 @@
     "bridgeErrorHandler": { "index": 2, "kind": "property", "displayName": 
"Bridge Error Handler", "group": "consumer", "label": "consumer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Allows for bridging the consumer to the Camel routing Error Handler, which 
mean any exceptions (if possible) occurred while the Camel consumer is trying 
to pickup incoming messages, or the like [...]
     "consumeDelete": { "index": 3, "kind": "property", "displayName": "Consume 
Delete", "group": "consumer", "label": "consumer", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": true, "configurationClass": 
"org.apache.camel.component.jooq.JooqConfiguration", "configurationField": 
"configuration", "description": "Delete entity after it is consumed" },
     "lazyStartProducer": { "index": 4, "kind": "property", "displayName": 
"Lazy Start Producer", "group": "producer", "label": "producer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Whether the producer should be started lazy (on the first message). By 
starting lazy you can use this to allow CamelContext and routes to startup in 
situations where a producer may otherwise fail [...]
-    "operation": { "index": 5, "kind": "property", "displayName": "Operation", 
"group": "producer", "label": "producer", "required": false, "type": "enum", 
"javaType": "org.apache.camel.component.jooq.JooqOperation", "enum": [ "NONE", 
"EXECUTE", "FETCH", "CONSUME" ], "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": "NONE", "configurationClass": 
"org.apache.camel.component.jooq.JooqConfiguration", "configurationField": 
"configuration", "description": "Type of oper [...]
+    "operation": { "index": 5, "kind": "property", "displayName": "Operation", 
"group": "producer", "label": "producer", "required": false, "type": "enum", 
"javaType": "org.apache.camel.component.jooq.JooqOperation", "enum": [ "NONE", 
"EXECUTE", "FETCH" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "NONE", "configurationClass": 
"org.apache.camel.component.jooq.JooqConfiguration", "configurationField": 
"configuration", "description": "Type of operation to ex [...]
     "query": { "index": 6, "kind": "property", "displayName": "Query", 
"group": "producer", "label": "producer", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.jooq.JooqConfiguration", "configurationField": 
"configuration", "description": "To execute plain SQL query" },
     "autowiredEnabled": { "index": 7, "kind": "property", "displayName": 
"Autowired Enabled", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether autowiring is enabled. This is used for automatic autowiring options 
(the option must be marked as autowired) by looking up in the registry to find 
if there is a single instance of matching t [...]
     "healthCheckConsumerEnabled": { "index": 8, "kind": "property", 
"displayName": "Health Check Consumer Enabled", "group": "health", "label": 
"health", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Used for enabling or disabling all consumer based health checks 
from this component" },
@@ -49,7 +49,7 @@
     "exceptionHandler": { "index": 5, "kind": "parameter", "displayName": 
"Exception Handler", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "object", "javaType": 
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", 
"deprecated": false, "autowired": false, "secret": false, "description": "To 
let the consumer use a custom ExceptionHandler. Notice if the option 
bridgeErrorHandler is enabled then this option is not in use. By def [...]
     "exchangePattern": { "index": 6, "kind": "parameter", "displayName": 
"Exchange Pattern", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "enum", "javaType": 
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], 
"deprecated": false, "autowired": false, "secret": false, "description": "Sets 
the exchange pattern when the consumer creates an exchange." },
     "pollStrategy": { "index": 7, "kind": "parameter", "displayName": "Poll 
Strategy", "group": "consumer (advanced)", "label": "consumer,advanced", 
"required": false, "type": "object", "javaType": 
"org.apache.camel.spi.PollingConsumerPollStrategy", "deprecated": false, 
"autowired": false, "secret": false, "description": "A pluggable 
org.apache.camel.PollingConsumerPollingStrategy allowing you to provide your 
custom implementation to control error handling usually occurred during the pol 
[...]
-    "operation": { "index": 8, "kind": "parameter", "displayName": 
"Operation", "group": "producer", "label": "producer", "required": false, 
"type": "enum", "javaType": "org.apache.camel.component.jooq.JooqOperation", 
"enum": [ "NONE", "EXECUTE", "FETCH", "CONSUME" ], "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": "NONE", 
"configurationClass": "org.apache.camel.component.jooq.JooqConfiguration", 
"configurationField": "configuration", "description": "Type of ope [...]
+    "operation": { "index": 8, "kind": "parameter", "displayName": 
"Operation", "group": "producer", "label": "producer", "required": false, 
"type": "enum", "javaType": "org.apache.camel.component.jooq.JooqOperation", 
"enum": [ "NONE", "EXECUTE", "FETCH" ], "deprecated": false, "autowired": 
false, "secret": false, "defaultValue": "NONE", "configurationClass": 
"org.apache.camel.component.jooq.JooqConfiguration", "configurationField": 
"configuration", "description": "Type of operation to e [...]
     "query": { "index": 9, "kind": "parameter", "displayName": "Query", 
"group": "producer", "label": "producer", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.jooq.JooqConfiguration", "configurationField": 
"configuration", "description": "To execute plain SQL query" },
     "lazyStartProducer": { "index": 10, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produ [...]
     "backoffErrorThreshold": { "index": 11, "kind": "parameter", 
"displayName": "Backoff Error Threshold", "group": "scheduler", "label": 
"consumer,scheduler", "required": false, "type": "integer", "javaType": "int", 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
number of subsequent error polls (failed due some error) that should happen 
before the backoffMultipler should kick-in." },
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jooq-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jooq-component.adoc
index cf61ef759ff8..915fd415469f 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jooq-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jooq-component.adoc
@@ -109,6 +109,9 @@ When using jooq as a producer you can use any of the 
following `JooqOperation` o
 | fetch | Execute a query (an `org.jooq.ResultQuery` in the message body, or 
the `query` option) and the `org.jooq.Result` of the query is stored as the new 
message body
 |===
 
+A message with no body (and no `query` option for `execute` and `fetch`) fails 
with an `InvalidPayloadException`.
+The `none` operation does not store an empty record.
+
 ==  Example
 
 .JOOQ configuration:
diff --git 
a/components/camel-jooq/src/generated/resources/META-INF/org/apache/camel/component/jooq/jooq.json
 
b/components/camel-jooq/src/generated/resources/META-INF/org/apache/camel/component/jooq/jooq.json
index bce0ae172257..d6c6e38ce898 100644
--- 
a/components/camel-jooq/src/generated/resources/META-INF/org/apache/camel/component/jooq/jooq.json
+++ 
b/components/camel-jooq/src/generated/resources/META-INF/org/apache/camel/component/jooq/jooq.json
@@ -29,7 +29,7 @@
     "bridgeErrorHandler": { "index": 2, "kind": "property", "displayName": 
"Bridge Error Handler", "group": "consumer", "label": "consumer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Allows for bridging the consumer to the Camel routing Error Handler, which 
mean any exceptions (if possible) occurred while the Camel consumer is trying 
to pickup incoming messages, or the like [...]
     "consumeDelete": { "index": 3, "kind": "property", "displayName": "Consume 
Delete", "group": "consumer", "label": "consumer", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": true, "configurationClass": 
"org.apache.camel.component.jooq.JooqConfiguration", "configurationField": 
"configuration", "description": "Delete entity after it is consumed" },
     "lazyStartProducer": { "index": 4, "kind": "property", "displayName": 
"Lazy Start Producer", "group": "producer", "label": "producer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Whether the producer should be started lazy (on the first message). By 
starting lazy you can use this to allow CamelContext and routes to startup in 
situations where a producer may otherwise fail [...]
-    "operation": { "index": 5, "kind": "property", "displayName": "Operation", 
"group": "producer", "label": "producer", "required": false, "type": "enum", 
"javaType": "org.apache.camel.component.jooq.JooqOperation", "enum": [ "NONE", 
"EXECUTE", "FETCH", "CONSUME" ], "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": "NONE", "configurationClass": 
"org.apache.camel.component.jooq.JooqConfiguration", "configurationField": 
"configuration", "description": "Type of oper [...]
+    "operation": { "index": 5, "kind": "property", "displayName": "Operation", 
"group": "producer", "label": "producer", "required": false, "type": "enum", 
"javaType": "org.apache.camel.component.jooq.JooqOperation", "enum": [ "NONE", 
"EXECUTE", "FETCH" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "NONE", "configurationClass": 
"org.apache.camel.component.jooq.JooqConfiguration", "configurationField": 
"configuration", "description": "Type of operation to ex [...]
     "query": { "index": 6, "kind": "property", "displayName": "Query", 
"group": "producer", "label": "producer", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.jooq.JooqConfiguration", "configurationField": 
"configuration", "description": "To execute plain SQL query" },
     "autowiredEnabled": { "index": 7, "kind": "property", "displayName": 
"Autowired Enabled", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether autowiring is enabled. This is used for automatic autowiring options 
(the option must be marked as autowired) by looking up in the registry to find 
if there is a single instance of matching t [...]
     "healthCheckConsumerEnabled": { "index": 8, "kind": "property", 
"displayName": "Health Check Consumer Enabled", "group": "health", "label": 
"health", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Used for enabling or disabling all consumer based health checks 
from this component" },
@@ -49,7 +49,7 @@
     "exceptionHandler": { "index": 5, "kind": "parameter", "displayName": 
"Exception Handler", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "object", "javaType": 
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", 
"deprecated": false, "autowired": false, "secret": false, "description": "To 
let the consumer use a custom ExceptionHandler. Notice if the option 
bridgeErrorHandler is enabled then this option is not in use. By def [...]
     "exchangePattern": { "index": 6, "kind": "parameter", "displayName": 
"Exchange Pattern", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "enum", "javaType": 
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], 
"deprecated": false, "autowired": false, "secret": false, "description": "Sets 
the exchange pattern when the consumer creates an exchange." },
     "pollStrategy": { "index": 7, "kind": "parameter", "displayName": "Poll 
Strategy", "group": "consumer (advanced)", "label": "consumer,advanced", 
"required": false, "type": "object", "javaType": 
"org.apache.camel.spi.PollingConsumerPollStrategy", "deprecated": false, 
"autowired": false, "secret": false, "description": "A pluggable 
org.apache.camel.PollingConsumerPollingStrategy allowing you to provide your 
custom implementation to control error handling usually occurred during the pol 
[...]
-    "operation": { "index": 8, "kind": "parameter", "displayName": 
"Operation", "group": "producer", "label": "producer", "required": false, 
"type": "enum", "javaType": "org.apache.camel.component.jooq.JooqOperation", 
"enum": [ "NONE", "EXECUTE", "FETCH", "CONSUME" ], "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": "NONE", 
"configurationClass": "org.apache.camel.component.jooq.JooqConfiguration", 
"configurationField": "configuration", "description": "Type of ope [...]
+    "operation": { "index": 8, "kind": "parameter", "displayName": 
"Operation", "group": "producer", "label": "producer", "required": false, 
"type": "enum", "javaType": "org.apache.camel.component.jooq.JooqOperation", 
"enum": [ "NONE", "EXECUTE", "FETCH" ], "deprecated": false, "autowired": 
false, "secret": false, "defaultValue": "NONE", "configurationClass": 
"org.apache.camel.component.jooq.JooqConfiguration", "configurationField": 
"configuration", "description": "Type of operation to e [...]
     "query": { "index": 9, "kind": "parameter", "displayName": "Query", 
"group": "producer", "label": "producer", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.jooq.JooqConfiguration", "configurationField": 
"configuration", "description": "To execute plain SQL query" },
     "lazyStartProducer": { "index": 10, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produ [...]
     "backoffErrorThreshold": { "index": 11, "kind": "parameter", 
"displayName": "Backoff Error Threshold", "group": "scheduler", "label": 
"consumer,scheduler", "required": false, "type": "integer", "javaType": "int", 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
number of subsequent error polls (failed due some error) that should happen 
before the backoffMultipler should kick-in." },
diff --git a/components/camel-jooq/src/main/docs/jooq-component.adoc 
b/components/camel-jooq/src/main/docs/jooq-component.adoc
index cf61ef759ff8..915fd415469f 100644
--- a/components/camel-jooq/src/main/docs/jooq-component.adoc
+++ b/components/camel-jooq/src/main/docs/jooq-component.adoc
@@ -109,6 +109,9 @@ When using jooq as a producer you can use any of the 
following `JooqOperation` o
 | fetch | Execute a query (an `org.jooq.ResultQuery` in the message body, or 
the `query` option) and the `org.jooq.Result` of the query is stored as the new 
message body
 |===
 
+A message with no body (and no `query` option for `execute` and `fetch`) fails 
with an `InvalidPayloadException`.
+The `none` operation does not store an empty record.
+
 ==  Example
 
 .JOOQ configuration:
diff --git 
a/components/camel-jooq/src/main/java/org/apache/camel/component/jooq/JooqConfiguration.java
 
b/components/camel-jooq/src/main/java/org/apache/camel/component/jooq/JooqConfiguration.java
index 0352e048a25a..778ab66c5a10 100644
--- 
a/components/camel-jooq/src/main/java/org/apache/camel/component/jooq/JooqConfiguration.java
+++ 
b/components/camel-jooq/src/main/java/org/apache/camel/component/jooq/JooqConfiguration.java
@@ -29,7 +29,7 @@ public class JooqConfiguration implements Cloneable {
     private Class<?> entityType;
 
     @UriParam(label = "producer", description = "Type of operation to execute 
on query", defaultValue = "NONE",
-              enums = "NONE,EXECUTE,FETCH,CONSUME")
+              enums = "NONE,EXECUTE,FETCH")
     private JooqOperation operation = JooqOperation.NONE;
 
     @UriParam(label = "consumer", defaultValue = "true", description = "Delete 
entity after it is consumed")
diff --git 
a/components/camel-jooq/src/main/java/org/apache/camel/component/jooq/JooqEndpoint.java
 
b/components/camel-jooq/src/main/java/org/apache/camel/component/jooq/JooqEndpoint.java
index a8b50cb6c5a1..f06597c44135 100644
--- 
a/components/camel-jooq/src/main/java/org/apache/camel/component/jooq/JooqEndpoint.java
+++ 
b/components/camel-jooq/src/main/java/org/apache/camel/component/jooq/JooqEndpoint.java
@@ -20,6 +20,7 @@ import org.apache.camel.Category;
 import org.apache.camel.Consumer;
 import org.apache.camel.Exchange;
 import org.apache.camel.Expression;
+import org.apache.camel.InvalidPayloadException;
 import org.apache.camel.NoTypeConversionAvailableException;
 import org.apache.camel.Processor;
 import org.apache.camel.Producer;
@@ -86,16 +87,20 @@ public class JooqEndpoint extends ScheduledPollEndpoint {
 
     protected Expression createProducerExpression() {
         final Class<?> type;
+        final String hint;
 
         switch (configuration.getOperation()) {
             case NONE:
                 type = configuration.getEntityType();
+                hint = "operation NONE needs a record of the entityType in the 
message body";
                 break;
             case EXECUTE:
                 type = Query.class;
+                hint = "operation EXECUTE needs an org.jooq.Query in the 
message body, or the query option";
                 break;
             case FETCH:
                 type = ResultQuery.class;
+                hint = "operation FETCH needs an org.jooq.ResultQuery in the 
message body, or the query option";
                 break;
             default:
                 throw new UnsupportedOperationException("Operation: " + 
configuration.getOperation());
@@ -111,7 +116,9 @@ public class JooqEndpoint extends ScheduledPollEndpoint {
                                 .wrapRuntimeCamelException(new 
NoTypeConversionAvailableException(defaultValue, type));
                     }
 
-                    answer = 
exchange.getContext().getInjector().newInstance(type);
+                    throw RuntimeCamelException
+                            .wrapRuntimeCamelException(
+                                    new InvalidPayloadException(exchange, 
type, exchange.getIn(), hint));
                 }
                 return answer;
             }
diff --git 
a/components/camel-jooq/src/test/java/org/apache/camel/component/jooq/JooqComponentTest.java
 
b/components/camel-jooq/src/test/java/org/apache/camel/component/jooq/JooqComponentTest.java
index a8c8c884b123..c22a309b1d37 100644
--- 
a/components/camel-jooq/src/test/java/org/apache/camel/component/jooq/JooqComponentTest.java
+++ 
b/components/camel-jooq/src/test/java/org/apache/camel/component/jooq/JooqComponentTest.java
@@ -16,8 +16,11 @@
  */
 package org.apache.camel.component.jooq;
 
+import java.util.Arrays;
+
 import org.apache.camel.PropertyBindingException;
 import org.apache.camel.component.jooq.db.tables.records.BookStoreRecord;
+import org.apache.camel.spi.UriParam;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -52,4 +55,13 @@ public class JooqComponentTest extends BaseJooqTest {
                 () -> component.createEndpoint(
                         
"jooq://org.apache.camel.component.jooq.db.tables.records.BookStoreRecord?operation=unexpectedOperation"));
     }
+
+    @Test
+    public void testOperationEnumsMatchJooqOperation() throws Exception {
+        // the enums of the operation option are what the catalog and the docs 
offer
+        UriParam uriParam = 
JooqConfiguration.class.getDeclaredField("operation").getAnnotation(UriParam.class);
+        String expected = String.join(",", 
Arrays.stream(JooqOperation.values()).map(Enum::name).sorted().toList());
+        String actual = String.join(",", 
Arrays.stream(uriParam.enums().split(",")).sorted().toList());
+        assertEquals(expected, actual);
+    }
 }
diff --git 
a/components/camel-jooq/src/test/java/org/apache/camel/component/jooq/JooqProducerEmptyBodyTest.java
 
b/components/camel-jooq/src/test/java/org/apache/camel/component/jooq/JooqProducerEmptyBodyTest.java
new file mode 100644
index 000000000000..cbda40133c56
--- /dev/null
+++ 
b/components/camel-jooq/src/test/java/org/apache/camel/component/jooq/JooqProducerEmptyBodyTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.jooq;
+
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.InvalidPayloadException;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.jooq.db.tables.records.BookStoreRecord;
+import org.apache.camel.util.ObjectHelper;
+import org.junit.jupiter.api.Test;
+
+import static org.apache.camel.component.jooq.db.Tables.BOOK_STORE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A message with no body must fail with a clear message, and must not store 
an empty record.
+ */
+public class JooqProducerEmptyBodyTest extends BaseJooqTest {
+
+    @Test
+    public void testStoreWithNoBodyFails() {
+        InvalidPayloadException cause = sendNullBody("direct:insert");
+        assertEquals(BookStoreRecord.class, cause.getType());
+        assertTrue(cause.getMessage().contains("operation NONE needs a record 
of the entityType"), cause.getMessage());
+
+        assertEquals(0, create.fetchCount(BOOK_STORE), "An empty record must 
not be stored");
+    }
+
+    @Test
+    public void testExecuteWithNoBodyFails() {
+        InvalidPayloadException cause = sendNullBody("direct:execute");
+        assertTrue(cause.getMessage().contains("operation EXECUTE needs an 
org.jooq.Query in the message body, or the query"),
+                cause.getMessage());
+    }
+
+    @Test
+    public void testFetchWithNoBodyFails() {
+        InvalidPayloadException cause = sendNullBody("direct:fetch");
+        assertTrue(
+                cause.getMessage().contains("operation FETCH needs an 
org.jooq.ResultQuery in the message body, or the query"),
+                cause.getMessage());
+    }
+
+    private InvalidPayloadException sendNullBody(String uri) {
+        ProducerTemplate producerTemplate = context.createProducerTemplate();
+        CamelExecutionException e = assertThrows(CamelExecutionException.class,
+                () -> producerTemplate.sendBody(uri, null));
+        InvalidPayloadException cause = 
ObjectHelper.getException(InvalidPayloadException.class, e);
+        assertNotNull(cause, "Expected an InvalidPayloadException but got: " + 
e);
+        return cause;
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        JooqComponent jooqComponent = (JooqComponent) 
context().getComponent("jooq");
+        JooqConfiguration jooqConfiguration = new JooqConfiguration();
+        jooqConfiguration.setDatabaseConfiguration(create.configuration());
+        jooqComponent.setConfiguration(jooqConfiguration);
+
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:insert")
+                        
.to("jooq://org.apache.camel.component.jooq.db.tables.records.BookStoreRecord");
+
+                from("direct:execute")
+                        
.to("jooq://org.apache.camel.component.jooq.db.tables.records.BookStoreRecord?operation=execute");
+
+                from("direct:fetch")
+                        
.to("jooq://org.apache.camel.component.jooq.db.tables.records.BookStoreRecord?operation=fetch");
+            }
+        };
+    }
+}
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 1bc188419d61..5e9a7ca6d673 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
@@ -986,6 +986,14 @@ with no stored metadata for the key, now logs a warning 
once per producer rather
 non-stateful key without a `keyLifecycleManager` stays silent, as there is no 
`maxKeyUsage` threshold to miss. For a
 stateful key the warning names the index-reuse risk; for any other key it 
names the `usageCount` that will not advance.
 
+=== camel-jooq - a message with no body fails
+
+The jooq producer now fails with an `InvalidPayloadException` when the message 
body is null and the `query` option is
+not set. Before, operation `NONE` (the default) created a new, empty record of 
the `entityType` and stored it, so a
+message with no body inserted an empty row (or failed with a database 
constraint error). Operations `EXECUTE` and
+`FETCH` failed with a `NoSuchMethodException` for `org.jooq.Query.<init>()`. 
Send the record, or the `org.jooq.Query`, in
+the message body. The `operation` option also no longer lists `CONSUME`, which 
was never a valid value.
+
 === camel-jsonpath - easy predicates with `+<=+` and `+>=+` match the boundary 
value
 
 An easy predicate (such as `+store.book.price <= 10+` or `+$.store.book.price 
>= 10+`) now matches the boundary

Reply via email to