- Overview
- Package structure
- Conceptual model
- Installation
- Wiring it up
- Usage examples
- Launching a job and waiting for a specific exit status
- Monitoring the latest execution of a job you did not launch yourself
- Waiting for every job to quiesce before shutdown
- Quiescence from outside the Spring context
- Restarting a failed execution
- Reading typed parameters inside a tasklet
- Tightening concurrency
- Behavioral reference
- Error model
- Defaults reference
- Schema and migrations
- Troubleshooting
- Limitations and known issues
- Summary
The smallmind-batch module is a thin, opinionated facade over
Spring Batch. It is organized as two
artifacts:
-
batch-base— a framework-neutral parameter envelope and aJobFactorycontract. No Spring dependency, no JDBC, no transitive Spring Batch classes. Safe to import from a service interface module, a shared model jar, or any library that wants to describe a batch invocation without pulling in the runtime that executes it. -
batch-spring— the Spring Batch implementation. Translates the neutral parameter wrappers into Spring’sJobParameters, discoversJobbeans from the application context, vends monitors and watchers for polling execution state, and ships a ready-to-import Spring XML configuration plus the Liquibase changelog that creates theBATCH_*metadata tables.
The module is small by design: six classes in batch-base (one enum, one
interface, one abstract parameter, three typed subclasses, and a convenience
map), eight classes in batch-spring, one Spring XML wiring file, and one
Liquibase changelog. It exists to turn Spring Batch into something a service
can adopt in an afternoon — without learning the full Spring Batch bean graph,
without hand-writing schema DDL, and without coupling the service’s public API
to Spring.
Spring Batch is powerful and comprehensive, but it has a shape problem for services whose batch work is a minor part of a larger application. A production Spring Batch setup needs, at minimum:
-
A
JobRepository(typically the JDBC-backedJobRepositoryFactoryBean), which in turn requires aDataSourceand aPlatformTransactionManager. -
A
JobOperator(orJobLauncher) with aTaskExecutorfor asynchronous launches. -
A
JobRegistry— or a disciplined convention for wiringJobbeans so that the operator can find them by name. -
A migration tool or a hand-crafted schema script to create the
BATCH_JOB_INSTANCE,BATCH_JOB_EXECUTION,BATCH_STEP_EXECUTION, and context tables plus the three sequences. -
A strategy for waiting on job completion, for monitoring exit status, and for restarting failed executions — none of which ships as a single plug-in-able component.
-
A way to pass caller-supplied parameters into a job. Spring Batch’s
JobParametersBuilderhas a method per Java type (addLong,addString,addDouble,addLocalDateTime, …) and anidentifyingflag per entry, so code that accepts parameters from somewhere else (a REST controller, a CLI argument parser, a scheduled trigger) always ends up doing the translation by hand.
smallmind-batch assembles (1) through (5) once, as an importable Spring XML
file plus a few collaborating beans, and solves (6) with a typed parameter
envelope that is framework-neutral on the contract side and Spring-aware on
the implementation side. A caller starts a job with two lines of code, waits
on it with three more, and never touches JobParametersBuilder or
JobRepositoryFactoryBean directly.
-
Defines a neutral parameter envelope (
BatchParameterand the typedBatchParametersmap) so callers can describe a job invocation without referencing any Spring Batch class. -
Implements
JobFactoryagainst Spring Batch (BatchJobFactory), translating eachBatchParameterentry into the correctJobParametersBuilder.addXxxcall and delegating to aJobOperator. -
Auto-discovers
Jobbeans (BatchJobRegistry) by scanning the application context duringBeanFactoryPostProcessor#postProcessBeanFactory— there is no@JobScope, no component-scan path, no manualregistercall. If a bean is assignable toJob, it is a job. -
Polls for completion at three levels of granularity: a single execution (
BatchJobMonitor), every execution visible through the repository with a configurable settling period (BatchJobWatcher), or every row in theBATCH_JOB_EXECUTIONtable read directly via JDBC (BatchJobInterrogator). -
Ships the
BATCH_*schema as Liquibase (Batch.changelog.xml), wired throughsmallmind-liquibaseso that the tables and the three sequences are created or migrated as part of the same application startup that initializes theJobRepository. -
Exposes a single task-executor factory bean (
BatchJobExecutorFactory) so the operator’s concurrency limit is configured as a plain integer property on an XML bean, rather than by assembling anExecutorServicein Java.
-
It does not define jobs, steps, tasklets, or item readers/writers. Those remain ordinary Spring Batch beans that the caller authors. This module launches, registers, monitors, and restarts them; it does not describe them.
-
It does not ship a REST or JMX admin console.
BatchJobFactoryis a Java API; wrapping it in a transport is the caller’s decision. -
It does not attempt to replace Spring Batch’s
JobOperatororJobRepository.BatchJobFactorycomposes them; it does not reimplement them. -
It does not support Spring Batch partitioning, remote chunking, or multi-node coordination.
BatchJobWatcherandBatchJobInterrogatorquery the sameJobRepository/ metadata tables the jobs write to, so they work correctly when all executions run on one node. Cross-node orchestration is out of scope.
| Class | Role |
|---|---|
|
Framework-neutral contract. Two methods: |
|
Abstract envelope carrying a typed value plus an |
|
Concrete subclasses, one per Spring Batch parameter flavor. Each pins
|
|
Discriminant enum with exactly four constants: |
|
|
| Class | Role |
|---|---|
|
Spring Batch implementation of |
|
|
|
|
|
Polls a single |
|
Polls every execution across every job instance and declares success when
all executions are |
|
Same idea as |
|
Static helper for tasklet bodies. One method today: |
|
|
| Resource | Role |
|---|---|
|
Composition root. Defines |
|
Defines |
|
Liquibase changelog that creates |
A few ideas recur throughout the module and are worth naming up front.
- Logical job name
-
The bean id under which a
Jobis declared in the Spring context. Passed toJobFactory.create(logicalName, …)and looked up throughBatchJobRegistry.getJob(name). There is no separate registration step: the bean id is the logical name. - Job instance vs. job execution
-
A job instance is the abstract identity of a run, determined by the job name plus the identifying subset of its parameters. A job execution is a concrete attempt against that instance — first run, retry, restart.
BatchJobFactory.createreturns the new execution id;restarttakes an execution id and produces a new one. - Identifying parameter
-
A parameter whose value contributes to the job instance identity key. Two launches with the same job name and the same identifying parameters target the same instance, which is why a duplicate launch with identical identifying parameters against a successfully completed instance throws
JobInstanceAlreadyCompleteException. Non-identifying parameters are still delivered to the job but are not part of the hash. - Settling period (
BatchJobWatcher) -
The minimum duration for which every visible execution must have been complete before the watcher returns success. Without this, a watcher that polled right after one job completed but before the next started would wrongly declare quiescence. The period is a caller decision; a couple of seconds is usually enough to bridge scheduler pulse intervals.
The two artifacts are published under org.smallmind:
<dependency>
<groupId>org.smallmind</groupId>
<artifactId>batch-base</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.smallmind</groupId>
<artifactId>batch-spring</artifactId>
<version>${project.version}</version>
</dependency>A reusable library that only needs to describe a batch launch (e.g., a
service API module whose REST controllers accept parameter maps and hand them
off to the runtime) depends on batch-base only. An application that wants to
actually run jobs depends on batch-spring, which transitively pulls in
batch-base, spring-batch-core, smallmind-liquibase, smallmind-nutsnbolts,
and scribe-pen for logging.
-
A JDBC data source addressable through four Spring properties:
jdbc.driver.classname,jdbc.url.batch,jdbc.username.batch, andjdbc.password.batch. These are consumed bybatch-liquibase.xmlto buildbatchDataSource. If you want to reuse an existingDataSourcebean instead, override thebatchDataSourcedefinition in your own context. -
A database type understood by Spring Batch’s
JobRepositoryFactoryBean. The shippedbatch.xmlsetsdatabaseType=mysql; override the property or the bean definition if you target a different engine. -
Liquibase on the classpath at runtime (
smallmind-liquibasepulls it in transitively). The changelog is run automatically during context startup through thebatchLiquibasebean.
The fastest path is to import batch.xml and define your jobs as ordinary
beans.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:batch="http://www.springframework.org/schema/batch"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/batch
http://www.springframework.org/schema/batch/spring-batch.xsd">
<import resource="classpath:org/smallmind/batch/spring/batch.xml"/> (1)
<batch:job id="nightlyReconciliation"> (2)
<batch:step id="reconcileStep">
<batch:tasklet ref="reconciliationTasklet"/>
</batch:step>
</batch:job>
<bean id="reconciliationTasklet"
class="com.example.batch.ReconciliationTasklet"/>
</beans>-
Brings in
jobRepository,jobRegistry,jobOperator,jobFactory,batchLiquibase, andbatchDataSource. -
The bean id (
nightlyReconciliation) is the logical name thatBatchJobRegistrywill discover and thatjobFactory.create(…)will accept. No further registration step is needed.
With that in place, any component that wants to launch the job takes a
dependency on jobFactory and calls create:
import java.time.LocalDateTime;
import org.smallmind.batch.base.BatchParameters;
import org.smallmind.batch.base.JobFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class ReconciliationLauncher {
private final JobFactory jobFactory;
@Autowired
public ReconciliationLauncher (JobFactory jobFactory) {
this.jobFactory = jobFactory;
}
public long launchForToday ()
throws Exception {
BatchParameters parameters = new BatchParameters();
parameters.putDate("runDate", LocalDateTime.now(), true); (1)
parameters.putString("dryRun", "false", false); (2)
return jobFactory.create("nightlyReconciliation", parameters, (3)
"scheduled nightly reconciliation");
}
}-
Identifying: two launches with the same
runDatehit the same job instance and the second will throwJobInstanceAlreadyCompleteExceptionif the first has already finished. -
Non-identifying: the job receives the value, but it does not participate in the instance hash, so flipping it between runs does not create a new instance.
-
The third argument is a free-form reason string written to the log. It is advisory only and may be
null.
BatchJobFactory.monitor(long) returns a BatchJobMonitor that polls the
execution until its ExitStatus matches one the caller cares about.
import java.util.concurrent.TimeUnit;
import org.smallmind.batch.base.BatchParameters;
import org.smallmind.batch.spring.BatchJobFactory;
import org.smallmind.batch.spring.BatchJobMonitor;
import org.springframework.batch.core.ExitStatus;
public class BlockingLauncher {
private final BatchJobFactory factory;
public BlockingLauncher (BatchJobFactory factory) {
this.factory = factory;
}
public boolean launchAndWaitForCompletion (String jobName, BatchParameters params)
throws Exception {
long executionId = factory.create(jobName, params, "synchronous caller");
BatchJobMonitor monitor = factory.monitor(executionId);
return monitor.await(5, TimeUnit.MINUTES, (1)
ExitStatus.COMPLETED, ExitStatus.FAILED); (2)
}
}-
Polling cadence is one-tenth of the total timeout, minimum one second — so this call wakes every 30 seconds.
-
Both
COMPLETEDandFAILEDare terminal states from the caller’s perspective. Supplying onlyCOMPLETEDwould cause the method to returnfalseon a failed run even though the job has definitively stopped.
Returning false from monitor.await means the timeout expired before any
of the requested statuses was observed; the job may still be running.
Returning true means one of the supplied statuses was observed — inspect
JobExecution.getExitStatus() if you need to distinguish COMPLETED from
FAILED.
monitorLatest(logicalName) resolves the most recent JobInstance for a
named job, then the most recent JobExecution against that instance, and
returns a monitor for it.
import java.util.concurrent.TimeUnit;
import org.smallmind.batch.spring.BatchJobFactory;
import org.smallmind.batch.spring.BatchJobMonitor;
import org.springframework.batch.core.ExitStatus;
public class StatusProbe {
public void waitForNightlyJob (BatchJobFactory factory)
throws Exception {
BatchJobMonitor monitor = factory.monitorLatest("nightlyReconciliation");
if (!monitor.await(10, TimeUnit.MINUTES, ExitStatus.COMPLETED)) {
throw new IllegalStateException("nightly reconciliation did not finish in time");
}
}
}If no instance has ever existed for the name — typically because the job bean
is declared but has never been launched — monitorLatest throws
FormattedNoSuchJobException rather than silently returning true.
BatchJobWatcher is the right tool for a coordinated shutdown: wait until
everything the repository knows about is done, and has been done for a
little while.
import java.util.concurrent.TimeUnit;
import org.smallmind.batch.spring.BatchJobFactory;
import org.smallmind.batch.spring.BatchJobWatcher;
public class ShutdownGate {
public void drain (BatchJobFactory factory)
throws InterruptedException {
BatchJobWatcher watcher = factory.watch();
if (!watcher.await(5, 120, TimeUnit.SECONDS)) { (1)
throw new IllegalStateException("batch activity did not quiesce");
}
}
}-
clear = 5 seconds,timeout = 120 seconds: every visible execution must be inCOMPLETEDstatus, continuously, for at least five seconds before the watcher returnstrue. The five-second floor is what keeps the watcher from declaring success during the gap between one execution finishing and the next being scheduled.
A deployment script, a blue/green cutover controller, or a database-side
maintenance gate may not have a JobRepository handy, but it does have the
BATCH_* tables. BatchJobInterrogator polls them with a single
SELECT COUNT(*) … WHERE STATUS != 'COMPLETED' per cycle.
import java.util.concurrent.TimeUnit;
import org.smallmind.batch.spring.BatchJobInterrogator;
public class ExternalGate {
public boolean waitForBatchIdle (BatchJobInterrogator interrogator)
throws Exception {
return interrogator.awaitQuiescence(10, TimeUnit.MINUTES);
}
}Wire the interrogator as a regular Spring bean when you want it inside a context:
<bean id="batchInterrogator" class="org.smallmind.batch.spring.BatchJobInterrogator">
<property name="dataSource" ref="batchDataSource"/>
<property name="schemaName" value="batch"/>
</bean>The schemaName property is concatenated into the SQL unqualified; pick a
value you trust.
JobFactory.restart(executionId) delegates to
JobOperator.restart(JobExecution), which re-runs the execution with the
same parameters. If the execution id cannot be found, a
FormattedJobRestartException (a NoSuchJobException subclass) is raised
with a formatted message identifying the id that was searched for.
public class RestartController {
private final JobFactory jobFactory;
public RestartController (JobFactory jobFactory) {
this.jobFactory = jobFactory;
}
public void retry (long previousExecutionId)
throws Exception {
jobFactory.restart(previousExecutionId);
}
}The restart uses the identifying parameters recorded in
BATCH_JOB_EXECUTION_PARAMS for the original execution, so it targets the
same job instance. Spring Batch decides from the instance’s state whether the
restart is permitted; a completed instance cannot be restarted and will throw
JobInstanceAlreadyCompleteException out of the underlying operator.
Tasklet bodies read parameters off the ChunkContext. TaskletUtility.asBoolean
is a one-liner convenience for the common case of a boolean flag passed as a
string parameter (Spring Batch does not have a native boolean parameter type,
so booleans are usually stored as "true" / "false" strings):
import org.smallmind.batch.spring.TaskletUtility;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
public class ReconciliationTasklet implements Tasklet {
@Override
public RepeatStatus execute (StepContribution contribution, ChunkContext chunkContext) {
boolean dryRun = TaskletUtility.asBoolean(chunkContext, "dryRun", false);
if (dryRun) {
// report what would happen, without mutating state
} else {
// perform the reconciliation
}
return RepeatStatus.FINISHED;
}
}For non-boolean types, read directly from
chunkContext.getStepContext().getJobParameters(); the map’s values are
already the correct Java type because JobParametersBuilder routed them
through the matching addXxx call at launch time.
The default concurrencyLimit on BatchJobExecutorFactory is 1, matching
the batch.xml wiring. A service that wants to run a small number of jobs in
parallel overrides the bean:
<bean id="jobOperator" class="org.springframework.batch.core.launch.support.SimpleJobOperator">
<property name="jobRepository" ref="jobRepository"/>
<property name="taskExecutor">
<bean class="org.smallmind.batch.spring.BatchJobExecutorFactory">
<property name="concurrencyLimit" value="4"/>
</bean>
</property>
</bean>Values below 1 are silently clamped to 1 — an empty pool would simply
swallow every submission and stall the operator, which is never the desired
outcome.
Spring Batch decides which instance a launch targets by hashing the
identifying subset of the supplied parameters. BatchParameter.isIdentifying()
is the only knob that controls this.
-
Making a parameter identifying ties it to the instance key. Two launches with the same identifying parameters will collide on the second attempt against an active or completed instance.
-
Making a parameter non-identifying decouples it from the key while still delivering it to the tasklet. Use this for inputs that influence behavior but should not create a new instance (e.g., a per-run correlation id, a
dryRunflag, or an audit trail note).
At least one identifying parameter — or a parameter set that is obviously
unique per run (a timestamp, a generation counter) — is essential for
repeatable jobs: otherwise, the second launch gets JobInstanceAlreadyComplete
on the first re-attempt.
All three waiting helpers (BatchJobMonitor.await, BatchJobWatcher.await,
BatchJobInterrogator.awaitQuiescence) share the same cadence rule:
-
Poll interval is
max(1000ms, timeout / 10). -
The final sleep before the deadline is shortened to
remainingso the call returns promptly when the timeout is about to expire.
The minimum-one-second floor is deliberate: it keeps these helpers from hammering the database with sub-second queries when the caller supplies a short total timeout.
The Spring Batch distinction is preserved and matters.
-
BatchStatusdescribes where an execution sits in the lifecycle:STARTING,STARTED,STOPPING,STOPPED,COMPLETED,FAILED,ABANDONED,UNKNOWN.BatchJobWatcherandBatchJobInterrogatorkey offCOMPLETEDin this set. -
ExitStatusis the higher-level outcome tag:COMPLETED,FAILED,STOPPED,NOOP,EXECUTING, plus any custom code the tasklet or step returns.BatchJobMonitor.awaitmatches against this set.
A job with BatchStatus.COMPLETED has stopped; its ExitStatus carries the
outcome interpretation. Match on whichever dimension your caller actually
cares about.
BatchJobFactory.create logs one line per launch at INFO through
scribe-pen:
Batch(nightlyReconciliation) for reason(scheduled nightly reconciliation) with parameters({runDate: [type=DATE,value=2026-04-21T03:00,identifying=true],dryRun: [type=STRING,value=false,identifying=false]})
This is the primary audit trail for launches. A null reason is rendered as
the literal string "unknown"; an empty or null parameter map as "none".
If a tighter audit record is needed, wrap the factory and emit whatever you
like before delegating.
-
BatchJobFactoryis safe for concurrentcreate,restart,monitor,monitorLatest, andwatchcalls from multiple threads — it delegates to the underlying repository and operator, both of which are designed for multi-threaded use. -
BatchJobMonitor,BatchJobWatcher, andBatchJobInterrogatorare single-use helpers. Each call toawaitis independent; there is no shared mutable state across calls and no cleanup required on the caller. -
BatchJobRegistryis populated once duringpostProcessBeanFactoryand thereafter read-only. The underlyingHashSetis never mutated after context startup, so concurrentgetJobNamesandgetJobcalls are safe. -
BatchJobExecutorFactoryis a singletonFactoryBean. TheTaskExecutorAdapterit vends wraps aScheduledThreadPoolExecutor, which is itself thread-safe.
The exceptions callers will see come in three rough groups.
- Pre-launch failures
-
Raised by
BatchJobFactory.createwhen the job name cannot be resolved (FormattedNoSuchJobException) or the parameter set fails Spring Batch’s validation (InvalidJobParametersException). - Launch-state failures
-
JobExecutionAlreadyRunningException(an active execution against the same instance already exists),JobInstanceAlreadyCompleteException(the parameters target a completed instance; supply a distinguishing identifying parameter), andJobRestartException(the instance is in a state Spring Batch considers non-restartable). All propagate out of the underlyingJobOperator. - Restart lookup failures
-
BatchJobFactory.restart(executionId)throwsFormattedJobRestartException(aNoSuchJobException) when no execution with that id can be located. Lookup of the latest execution by logical name throwsFormattedNoSuchJobExceptionwith the same parent type.
Both formatted exceptions exist purely so that the diagnostic (the logical
name or the execution id) can be embedded in the message without a separate
String.format call at every throw site.
| Setting | Default | Where set |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Poll floor (all |
|
|
|
caller’s |
|
|
literal string |
|
Batch.changelog.xml creates the standard Spring Batch metadata schema as of
Spring Batch 5 (the JSR-352 artifacts are not included):
-
BATCH_JOB_INSTANCE— one row per distinct (job name, identifying parameter set). -
BATCH_JOB_EXECUTION— one row per launch or restart attempt. -
BATCH_JOB_EXECUTION_PARAMS— the parameter values recorded at launch. Each row has aPARAMETER_TYPEcolumn holding one of Spring Batch’s type names (STRING,LONG,DOUBLE,LOCAL_DATE_TIME, …), which is whyParameterTypehas exactly the four constants it does. -
BATCH_JOB_EXECUTION_CONTEXT/BATCH_STEP_EXECUTION_CONTEXT— the serialized execution context (job-scoped and step-scoped respectively). -
BATCH_STEP_EXECUTION— one row per step attempt within an execution. -
BATCH_JOB_INSTANCE_SEQ/BATCH_JOB_EXECUTION_SEQ/BATCH_STEP_EXECUTION_SEQ— the three single-row tables Spring Batch uses as sequences on engines without native sequence support. -
Foreign keys, the
JOB_INST_UNunique constraint on(JOB_NAME, JOB_KEY), and a set of non-unique indexes on the most commonly filtered columns.
The changelog is run on the batchDataSource. If your application already
manages its own Liquibase runner, you can pull just the changelog file in
yourself:
<bean id="applicationLiquibase" class="org.smallmind.liquibase.spring.SpringLiquibase">
<property name="dataSource" ref="myDataSource"/>
<property name="source" value="CLASSPATH"/>
<property name="goal" value="UPDATE"/>
<property name="changeLogs">
<list>
<!-- application-specific changelogs here -->
<bean class="org.smallmind.liquibase.spring.ChangeLog">
<property name="input" value="org/smallmind/batch/spring/Batch.changelog.xml"/>
<property name="output" value="Batch.changelog.xml"/>
</bean>
</list>
</property>
</bean>In that case, omit batch-liquibase.xml from your imports and define your
own batchDataSource bean directly.
NoSuchJobExceptionat launch time-
BatchJobRegistrydid not discover a bean assignable toJobunder the name you passed. Check: (a) that the job bean id exactly matches the logical name, (b) thatBatchJobRegistryis present in the context (it is wired automatically viabatch.xml), and (c) that the bean is declared before the context is refreshed — the registry scans duringpostProcessBeanFactory, not lazily. JobInstanceAlreadyCompleteExceptionon every second launch-
None of your parameters were identifying, so every launch hashes to the same instance key. Mark at least one parameter identifying, typically a timestamp or a correlation id.
BatchJobWatcher.awaitreturnsfalsedespite everything looking idle-
Either the timeout was shorter than the clear period, or an execution just finished inside the clear window. The clear period resets whenever
hasCompleted()observes any non-COMPLETEDexecution; immediately after a new run starts, the clock starts over. - Liquibase reports "table already exists" on startup
-
Another component created the
BATCH_*tables independently. Either remove the duplicate schema creation path or set thebatch-liquibase.xmlchangelog’sMODEproperty (through `smallmind-liquibase’s own configuration knobs) so Liquibase can synchronise rather than re-create. BatchJobInterrogatorreturnsfalsebut no jobs are visible in the UI-
The interrogator reads by
STATUS != 'COMPLETED', which includesFAILED,STOPPED,ABANDONED, andUNKNOWN. A long-dead failed execution keeps the interrogator unhappy until it is either restarted toCOMPLETEDor administratively removed. This is intentional — a failed execution that nobody has decided how to handle is not idle.
- Single-node coordination
-
BatchJobWatcherandBatchJobInterrogatoronly see executions that live in the repository they query. In a multi-node cluster where each node has its own operator, a watcher on node A cannot detect activity submitted on node B unless both share the sameJobRepositoryschema. - No boolean parameter type
-
Spring Batch does not have a native boolean parameter; booleans travel as strings and are parsed on the tasklet side.
BatchParametersmirrors this: there is noputBoolean. UseputString(key, "true", …)andTaskletUtility.asBooleanat the read site. - No manual job registration
-
BatchJobRegistry.registerandBatchJobRegistry.unregisterthrowUnsupportedOperationException. If you need dynamic job construction, expose the constructedJobas a bean before context refresh or run a second context with the additional beans included; the registry does not support mutation after startup. BatchJobInterrogatorreads counts, not structure-
It answers "are there any non-completed executions?" and nothing else. A caller that wants a richer view (which jobs, which steps, which parameters) should query the
BATCH_*tables directly or use aJobRepository.
smallmind-batch makes Spring Batch approachable: a neutral parameter
envelope that keeps upstream callers off the Spring Batch API, a ready-to-import
XML composition root that wires the five beans every Spring Batch deployment
needs, a registry that discovers jobs automatically, and three polling helpers
that cover the waiting patterns a service actually uses. batch-base is the
contract edge (JobFactory, BatchParameters); batch-spring is the
runtime. Import batch.xml, declare your jobs as beans, and launch them
through jobFactory.