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 e6d529b9d3e3 CAMEL-26122: camel-spring-batch - skip null headers and
honour jobFromHeader=false (#28899)
e6d529b9d3e3 is described below
commit e6d529b9d3e332461daef0402d261d7702303692
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 2 20:38:24 2026 +0100
CAMEL-26122: camel-spring-batch - skip null headers and honour
jobFromHeader=false (#28899)
…mHeader=false
A message header with a null value was added as a String job parameter with
a
null value, which Spring Batch 6 rejects, so the job launch failed. Such
headers are now skipped.
The CamelSpringBatchJobName header chose the job to launch even with
jobFromHeader=false. The producer now reads that header only when the
endpoint has jobFromHeader=true. Docs and upgrade guide updated.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../camel/catalog/components/spring-batch.json | 4 +--
.../camel/catalog/docs/spring-batch-component.adoc | 8 +++++-
.../camel/component/spring/batch/spring-batch.json | 4 +--
.../src/main/docs/spring-batch-component.adoc | 8 +++++-
.../spring/batch/SpringBatchConstants.java | 3 ++-
.../spring/batch/SpringBatchEndpoint.java | 2 +-
.../spring/batch/SpringBatchProducer.java | 15 +++++++----
.../spring/batch/SpringBatchEndpointTest.java | 31 ++++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 10 +++++++
.../dsl/SpringBatchEndpointBuilderFactory.java | 3 ++-
10 files changed, 74 insertions(+), 14 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/spring-batch.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/spring-batch.json
index d1c8fbc4e8e9..a111a164230b 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/spring-batch.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/spring-batch.json
@@ -30,10 +30,10 @@
"autowiredEnabled": { "index": 3, "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 [...]
},
"headers": {
- "CamelSpringBatchJobName": { "index": 0, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The name of the Batch Job to use", "constantName":
"org.apache.camel.component.spring.batch.SpringBatchConstants#JOB_NAME" }
+ "CamelSpringBatchJobName": { "index": 0, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The name of the Batch Job to use. Only used when the
endpoint has jobFromHeader=true.", "constantName":
"org.apache.camel.component.spring.batch.SpringBatchConstants#JOB_NAME" }
},
"bodyTypes": {
- "producerIn": { "carriers": [ "any" ], "description": "The body is not
read: the job parameters are the message headers (Date, Long and Double values
as such, other values as text; a null header value fails)" },
+ "producerIn": { "carriers": [ "any" ], "description": "The body is not
read: the job parameters are the message headers (Date, Long and Double values
as such, other values as text; headers with a null value are skipped)" },
"producerOut": { "carriers": [ "pojo" ], "javaType":
"org.springframework.batch.core.job.JobExecution", "description": "The
JobExecution of the launched job, with its status and exit status; the headers
stay as they were" }
},
"properties": {
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-batch-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-batch-component.adoc
index d3b20e6fe115..8a4e507ac76e 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-batch-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-batch-component.adoc
@@ -74,7 +74,13 @@ Registry, then use it.
All headers found in the message are passed to the `JobLauncher` as job
parameters. `String`, `Long`, `Double` and `java.util.Date` values are
copied to the `org.springframework.batch.core.JobParametersBuilder` -
-other data types are converted to Strings.
+other data types are converted to Strings. Headers with a `null` value are
+skipped, as Spring Batch does not accept a job parameter without a value.
+
+By default, the job to launch is the one named in the endpoint URI. To pick the
+job per message, set `jobFromHeader=true` on the endpoint and put the job name
in
+the `CamelSpringBatchJobName` header. Without `jobFromHeader=true` that header
+is ignored when choosing the job.
== Examples
diff --git
a/components/camel-spring-parent/camel-spring-batch/src/generated/resources/META-INF/org/apache/camel/component/spring/batch/spring-batch.json
b/components/camel-spring-parent/camel-spring-batch/src/generated/resources/META-INF/org/apache/camel/component/spring/batch/spring-batch.json
index d1c8fbc4e8e9..a111a164230b 100644
---
a/components/camel-spring-parent/camel-spring-batch/src/generated/resources/META-INF/org/apache/camel/component/spring/batch/spring-batch.json
+++
b/components/camel-spring-parent/camel-spring-batch/src/generated/resources/META-INF/org/apache/camel/component/spring/batch/spring-batch.json
@@ -30,10 +30,10 @@
"autowiredEnabled": { "index": 3, "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 [...]
},
"headers": {
- "CamelSpringBatchJobName": { "index": 0, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The name of the Batch Job to use", "constantName":
"org.apache.camel.component.spring.batch.SpringBatchConstants#JOB_NAME" }
+ "CamelSpringBatchJobName": { "index": 0, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The name of the Batch Job to use. Only used when the
endpoint has jobFromHeader=true.", "constantName":
"org.apache.camel.component.spring.batch.SpringBatchConstants#JOB_NAME" }
},
"bodyTypes": {
- "producerIn": { "carriers": [ "any" ], "description": "The body is not
read: the job parameters are the message headers (Date, Long and Double values
as such, other values as text; a null header value fails)" },
+ "producerIn": { "carriers": [ "any" ], "description": "The body is not
read: the job parameters are the message headers (Date, Long and Double values
as such, other values as text; headers with a null value are skipped)" },
"producerOut": { "carriers": [ "pojo" ], "javaType":
"org.springframework.batch.core.job.JobExecution", "description": "The
JobExecution of the launched job, with its status and exit status; the headers
stay as they were" }
},
"properties": {
diff --git
a/components/camel-spring-parent/camel-spring-batch/src/main/docs/spring-batch-component.adoc
b/components/camel-spring-parent/camel-spring-batch/src/main/docs/spring-batch-component.adoc
index d3b20e6fe115..8a4e507ac76e 100644
---
a/components/camel-spring-parent/camel-spring-batch/src/main/docs/spring-batch-component.adoc
+++
b/components/camel-spring-parent/camel-spring-batch/src/main/docs/spring-batch-component.adoc
@@ -74,7 +74,13 @@ Registry, then use it.
All headers found in the message are passed to the `JobLauncher` as job
parameters. `String`, `Long`, `Double` and `java.util.Date` values are
copied to the `org.springframework.batch.core.JobParametersBuilder` -
-other data types are converted to Strings.
+other data types are converted to Strings. Headers with a `null` value are
+skipped, as Spring Batch does not accept a job parameter without a value.
+
+By default, the job to launch is the one named in the endpoint URI. To pick the
+job per message, set `jobFromHeader=true` on the endpoint and put the job name
in
+the `CamelSpringBatchJobName` header. Without `jobFromHeader=true` that header
+is ignored when choosing the job.
== Examples
diff --git
a/components/camel-spring-parent/camel-spring-batch/src/main/java/org/apache/camel/component/spring/batch/SpringBatchConstants.java
b/components/camel-spring-parent/camel-spring-batch/src/main/java/org/apache/camel/component/spring/batch/SpringBatchConstants.java
index a38766019473..3a3d23d34569 100644
---
a/components/camel-spring-parent/camel-spring-batch/src/main/java/org/apache/camel/component/spring/batch/SpringBatchConstants.java
+++
b/components/camel-spring-parent/camel-spring-batch/src/main/java/org/apache/camel/component/spring/batch/SpringBatchConstants.java
@@ -25,7 +25,8 @@ public final class SpringBatchConstants {
public static final String DEFAULT_JOB_LAUNCHER_REF_NAME = "jobLauncher";
- @Metadata(description = "The name of the Batch Job to use", javaType =
"String")
+ @Metadata(description = "The name of the Batch Job to use. Only used when
the endpoint has jobFromHeader=true.",
+ javaType = "String")
public static final String JOB_NAME = "CamelSpringBatchJobName";
private SpringBatchConstants() {
diff --git
a/components/camel-spring-parent/camel-spring-batch/src/main/java/org/apache/camel/component/spring/batch/SpringBatchEndpoint.java
b/components/camel-spring-parent/camel-spring-batch/src/main/java/org/apache/camel/component/spring/batch/SpringBatchEndpoint.java
index f8ce01d07243..8aa5278e4925 100644
---
a/components/camel-spring-parent/camel-spring-batch/src/main/java/org/apache/camel/component/spring/batch/SpringBatchEndpoint.java
+++
b/components/camel-spring-parent/camel-spring-batch/src/main/java/org/apache/camel/component/spring/batch/SpringBatchEndpoint.java
@@ -41,7 +41,7 @@ import org.springframework.batch.core.launch.JobLauncher;
remote = false, producerOnly = true, headersClass =
SpringBatchConstants.class, category = { Category.WORKFLOW })
@BodyTypes(producerIn = @BodyType(value = Carrier.ANY,
description = "The body is not read: the job
parameters are the message headers (Date, Long and"
- + " Double values as such,
other values as text; a null header value fails)"),
+ + " Double values as such,
other values as text; headers with a null value are skipped)"),
producerOut = @BodyType(value = Carrier.POJO, javaType =
"org.springframework.batch.core.job.JobExecution",
description = "The JobExecution of the
launched job, with its status and exit status; the"
+ " headers stay as they
were"))
diff --git
a/components/camel-spring-parent/camel-spring-batch/src/main/java/org/apache/camel/component/spring/batch/SpringBatchProducer.java
b/components/camel-spring-parent/camel-spring-batch/src/main/java/org/apache/camel/component/spring/batch/SpringBatchProducer.java
index b7a98a73924e..b327e9c4bca1 100644
---
a/components/camel-spring-parent/camel-spring-batch/src/main/java/org/apache/camel/component/spring/batch/SpringBatchProducer.java
+++
b/components/camel-spring-parent/camel-spring-batch/src/main/java/org/apache/camel/component/spring/batch/SpringBatchProducer.java
@@ -50,10 +50,17 @@ public class SpringBatchProducer extends DefaultProducer {
this.jobRegistry = jobRegistry;
}
+ @Override
+ public SpringBatchEndpoint getEndpoint() {
+ return (SpringBatchEndpoint) super.getEndpoint();
+ }
+
@Override
public void process(Exchange exchange) throws Exception {
JobParameters jobParameters =
prepareJobParameters(exchange.getIn().getHeaders());
- String messageJobName =
jobParameters.getString(SpringBatchConstants.JOB_NAME);
+ // the job name header is only used when the endpoint is configured
with jobFromHeader=true
+ String messageJobName
+ = getEndpoint().isJobFromHeader() ?
jobParameters.getString(SpringBatchConstants.JOB_NAME) : null;
Job job2run = this.job;
@@ -83,7 +90,7 @@ public class SpringBatchProducer extends DefaultProducer {
/**
* Helper method converting the Camel message headers into the Spring
Batch parameters map. Date, Long and Double
* header values are converted to the appropriate types. All the other
header values are converted to string
- * representation.
+ * representation. Headers with a null value are skipped.
*
* @param headers Camel message header to be converted
* @return Camel message headers converted into the Spring Batch
parameters map
@@ -101,10 +108,8 @@ public class SpringBatchProducer extends DefaultProducer {
parametersBuilder.addDouble(headerKey, (Double) headerValue);
} else if (headerValue != null) {
parametersBuilder.addString(headerKey, headerValue.toString());
- } else {
- // if the value is null we just put String with null value
here to avoid the NPE
- parametersBuilder.addString(headerKey, null);
}
+ // a header with a null value is skipped, as Spring Batch does not
accept a job parameter without a value
}
JobParameters jobParameters = parametersBuilder.toJobParameters();
LOG.debug("Prepared parameters for Spring Batch job: {}",
jobParameters);
diff --git
a/components/camel-spring-parent/camel-spring-batch/src/test/java/org/apache/camel/component/spring/batch/SpringBatchEndpointTest.java
b/components/camel-spring-parent/camel-spring-batch/src/test/java/org/apache/camel/component/spring/batch/SpringBatchEndpointTest.java
index 231a75299072..169655158122 100644
---
a/components/camel-spring-parent/camel-spring-batch/src/test/java/org/apache/camel/component/spring/batch/SpringBatchEndpointTest.java
+++
b/components/camel-spring-parent/camel-spring-batch/src/test/java/org/apache/camel/component/spring/batch/SpringBatchEndpointTest.java
@@ -17,6 +17,8 @@
package org.apache.camel.component.spring.batch;
import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
import org.apache.camel.CamelContext;
import org.apache.camel.EndpointInject;
@@ -41,11 +43,13 @@ import
org.springframework.batch.core.job.parameters.JobParameters;
import org.springframework.batch.core.launch.JobLauncher;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.BDDMockito.any;
import static org.mockito.BDDMockito.eq;
import static org.mockito.BDDMockito.mock;
+import static org.mockito.BDDMockito.never;
import static org.mockito.BDDMockito.verify;
import static org.mockito.BDDMockito.when;
@@ -156,6 +160,33 @@ public class SpringBatchEndpointTest extends
CamelTestSupport {
assertEquals(parameter, headerValue);
}
+ @Test
+ public void shouldSkipNullHeadersWhenConvertingToJobParams() throws
Exception {
+ // Given
+ Map<String, Object> headers = new HashMap<>();
+ headers.put("nullHeader", null);
+ headers.put("headerKey", "headerValue");
+
+ // When
+ template.sendBodyAndHeaders("direct:start", "Start the job, please.",
headers);
+
+ // Then
+ ArgumentCaptor<JobParameters> jobParameters =
ArgumentCaptor.forClass(JobParameters.class);
+ verify(jobLauncher).run(eq(job), jobParameters.capture());
+ assertNull(jobParameters.getValue().getParameter("nullHeader"));
+ assertEquals("headerValue",
jobParameters.getValue().getString("headerKey"));
+ }
+
+ @Test
+ public void shouldIgnoreJobNameHeaderWhenJobFromHeaderIsFalse() throws
Exception {
+ // When
+ template.sendBodyAndHeader("direct:start", "Start the job, please.",
SpringBatchConstants.JOB_NAME, "dynamicMockjob");
+
+ // Then
+ verify(jobLauncher).run(eq(job), any(JobParameters.class));
+ verify(jobLauncher, never()).run(eq(dynamicMockjob),
any(JobParameters.class));
+ }
+
@Test
public void shouldConvertDateHeadersToJobParams() throws Exception {
// Given
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 b0657a286b5f..f50cb24f54d9 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
@@ -737,6 +737,16 @@ CamelContext of that provider. It now closes each of them
only when no other `JC
it returned the closed cache, whose operations throw `IllegalStateException`.
This matters for code that calls a
`JCacheManager` (for example one created by a custom `JCacheManagerFactory`)
after it was closed.
+=== camel-spring-batch - the CamelSpringBatchJobName header needs
jobFromHeader=true
+
+The producer now takes the job name from the `CamelSpringBatchJobName` header
only when the endpoint has
+`jobFromHeader=true`. Before, the header chose which job was launched also
with the default `jobFromHeader=false`,
+instead of the job named in the endpoint URI. Set `jobFromHeader=true` on the
endpoint to keep picking the job from
+the header.
+
+A message header with a `null` value is no longer turned into a job parameter.
Before, such a header failed the job
+launch, because Spring Batch does not accept a job parameter without a value.
+
=== camel-spring-rabbitmq - confirm=auto waits for simple publisher confirms
With the default `confirm=auto`, an InOnly producer now also waits for the
publisher confirm when the connection
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SpringBatchEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SpringBatchEndpointBuilderFactory.java
index 77f732bcd484..12a29a4885ca 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SpringBatchEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SpringBatchEndpointBuilderFactory.java
@@ -265,7 +265,8 @@ public interface SpringBatchEndpointBuilderFactory {
public static final SpringBatchHeaderNameBuilder INSTANCE = new
SpringBatchHeaderNameBuilder();
/**
- * The name of the Batch Job to use.
+ * The name of the Batch Job to use. Only used when the endpoint has
+ * jobFromHeader=true.
*
* The option is a: {@code String} type.
*