Appearance
Close of Business (COB) - Technical Guide
The Loan COB job is Apache Fineract's nightly batch process. It runs arrears calculations, applies overdue charges, posts accrual journal entries, and advances the business date across every active loan in the portfolio. This guide covers its configuration, worker architecture, and operational management.
See the Close of Business feature page for a business-level explanation of what COB does and why it matters.
How COB Is Triggered
COB is a Spring Batch job named Loan COB. It is scheduled by the batch manager node and executed by one or more batch worker nodes. The manager partitions the loan portfolio into chunks, distributes them to workers via the remote job messaging system, and waits for all partitions to complete before finalising the run and advancing the business date.
On a single-node deployment (the default), the same process acts as both manager and worker. On scaled deployments, the roles are separated.
Environment Variables
Enabling COB
| Variable | Default | Description |
|---|---|---|
FINERACT_JOB_LOAN_COB_ENABLED | true | Enable the Loan COB job. Set to false only if COB is managed externally via direct API triggers. |
FINERACT_MODE_BATCH_MANAGER_ENABLED | true | This node schedules and partitions COB. |
FINERACT_MODE_BATCH_WORKER_ENABLED | true | This node executes COB partitions. |
Tuning Chunk and Partition Size
| Variable | Default | Description |
|---|---|---|
LOAN_COB_CHUNK_SIZE | 10 | Loans processed per step chunk within a single worker. Increase for faster single-worker throughput; decrease if individual chunks are hitting timeouts. |
LOAN_COB_PARTITION_SIZE | 10 | Loans per partition distributed across workers. Increase to reduce partition count (and manager overhead) for large portfolios. |
LOAN_COB_POLL_INTERVAL | 1000 | Milliseconds workers wait between polling for available partitions. Reduce to speed up partition pickup; increase to reduce polling load. |
FINERACT_JOB_STUCK_RETRY_THRESHOLD | 5 | How many times a stuck or failing partition is retried before being marked failed. |
Task Executor Pool
| Variable | Default | Description |
|---|---|---|
FINERACT_DEFAULT_TASK_EXECUTOR_CORE_POOL_SIZE | 10 | Core thread pool size for async task execution (including COB partition steps). |
FINERACT_DEFAULT_TASK_EXECUTOR_MAX_POOL_SIZE | 100 | Maximum thread pool size. |
Single-Node vs Multi-Node Architecture
Single Node (Default)
All four modes are enabled on the same instance:
bash
FINERACT_MODE_READ_ENABLED=true
FINERACT_MODE_WRITE_ENABLED=true
FINERACT_MODE_BATCH_MANAGER_ENABLED=true
FINERACT_MODE_BATCH_WORKER_ENABLED=true
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_SPRING_EVENTS_ENABLED=trueIn single-node mode, job messages pass via in-process Spring events - no external broker is needed for COB coordination.
This is sufficient for portfolios up to a few tens of thousands of loans, depending on the COB processing window available.
Multi-Node (Scaled Deployments)
For large portfolios, separate the manager and worker roles across multiple instances. The manager partitions the work; workers process partitions in parallel:
bash
# Manager node
FINERACT_NODE_ID=1
FINERACT_MODE_READ_ENABLED=true
FINERACT_MODE_WRITE_ENABLED=true
FINERACT_MODE_BATCH_MANAGER_ENABLED=true
FINERACT_MODE_BATCH_WORKER_ENABLED=false
FINERACT_LIQUIBASE_ENABLED=true
# Worker node (one or more)
FINERACT_NODE_ID=2
FINERACT_MODE_READ_ENABLED=false
FINERACT_MODE_WRITE_ENABLED=false
FINERACT_MODE_BATCH_MANAGER_ENABLED=false
FINERACT_MODE_BATCH_WORKER_ENABLED=true
FINERACT_LIQUIBASE_ENABLED=falseIn multi-node mode, in-process Spring events cannot cross JVM boundaries. An external message broker is required.
WARNING
FINERACT_NODE_ID must be unique across all nodes in a multi-node deployment. Nodes sharing the same ID will cause job coordination failures.
Remote Job Messaging (Multi-Node Transport)
The remote job messaging system carries partition work items from the manager to workers. It is separate from the external business events system - do not use the same Kafka topic or JMS queue for both.
Exactly one transport must be enabled. The three options are:
Spring Events (Single-Node Only)
bash
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_SPRING_EVENTS_ENABLED=true
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_JMS_ENABLED=false
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_KAFKA_ENABLED=falseJMS / ActiveMQ
bash
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_SPRING_EVENTS_ENABLED=false
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_JMS_ENABLED=true
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_JMS_BROKER_URL=tcp://activemq:61616
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_JMS_QUEUE_NAME=JMS-request-queue
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_JMS_BROKER_USERNAME=fineract
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_JMS_BROKER_PASSWORD=changeme
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_KAFKA_ENABLED=falseKafka
bash
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_SPRING_EVENTS_ENABLED=false
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_JMS_ENABLED=false
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_KAFKA_ENABLED=true
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_KAFKA_BOOTSTRAP_SERVERS=kafka:9092
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_KAFKA_TOPIC_NAME=job-topic
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_KAFKA_TOPIC_PARTITIONS=10
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_KAFKA_TOPIC_REPLICAS=3
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_KAFKA_TOPIC_AUTO_CREATE=true
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_KAFKA_CONSUMER_GROUPID=fineract-consumer-group-idOnly one transport at a time
Enabling more than one transport simultaneously causes duplicate job dispatch. Enable exactly one and set the others to false.
Docker Compose Example (Multi-Node with Kafka)
yaml
services:
kafka:
image: apache/kafka:latest
ports:
- "9092:9092"
fineract-manager:
image: apache/fineract:latest
environment:
FINERACT_NODE_ID: "1"
FINERACT_MODE_BATCH_MANAGER_ENABLED: "true"
FINERACT_MODE_BATCH_WORKER_ENABLED: "false"
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_SPRING_EVENTS_ENABLED: "false"
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_KAFKA_ENABLED: "true"
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_KAFKA_BOOTSTRAP_SERVERS: "kafka:9092"
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_KAFKA_TOPIC_NAME: "job-topic"
LOAN_COB_PARTITION_SIZE: "100"
fineract-worker:
image: apache/fineract:latest
environment:
FINERACT_NODE_ID: "2"
FINERACT_MODE_BATCH_MANAGER_ENABLED: "false"
FINERACT_MODE_BATCH_WORKER_ENABLED: "true"
FINERACT_LIQUIBASE_ENABLED: "false"
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_SPRING_EVENTS_ENABLED: "false"
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_KAFKA_ENABLED: "true"
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_KAFKA_BOOTSTRAP_SERVERS: "kafka:9092"
FINERACT_REMOTE_JOB_MESSAGE_HANDLER_KAFKA_TOPIC_NAME: "job-topic"
LOAN_COB_CHUNK_SIZE: "50"Scale the worker service horizontally to reduce the COB processing window. Each additional worker processes partitions in parallel. Ensure each worker has a distinct FINERACT_NODE_ID.
Scheduling COB
COB is scheduled via the Fineract Scheduler API. The schedule is stored per-tenant in the database and persists across restarts. Modify it via the API:
bash
# List all jobs and find the Loan COB job ID
curl https://your-instance.finecko.com/fineract-provider/api/v1/jobs \
-H "Fineract-Platform-TenantId: default" \
-H "Authorization: Basic $(echo -n 'mifos:password' | base64)"
# Update the schedule (cron expression)
curl -X PUT \
https://your-instance.finecko.com/fineract-provider/api/v1/jobs/1 \
-H "Fineract-Platform-TenantId: default" \
-H "Authorization: Basic $(echo -n 'mifos:password' | base64)" \
-H "Content-Type: application/json" \
-d '{
"cronExpression": "0 0 1 * * ?",
"active": true
}'The cron expression uses Quartz format: seconds minutes hours day-of-month month day-of-week. 0 0 1 * * ? schedules COB daily at 01:00 server time.
Timezone note: the scheduler uses the timezone configured for the tenant in the fineract_tenants.tenants table. Confirm the tenant timezone is set correctly before scheduling production COB runs. An institution in Africa/Nairobi (UTC+3) running COB at 0 0 1 * * ? will execute at 01:00 East Africa Time, not 01:00 UTC.
Manually Triggering COB
COB can be triggered immediately via the API without waiting for the scheduled run:
bash
curl -X POST \
https://your-instance.finecko.com/fineract-provider/api/v1/jobs/1/run \
-H "Fineract-Platform-TenantId: default" \
-H "Authorization: Basic $(echo -n 'mifos:password' | base64)"This is useful during initial setup to verify that COB completes correctly, or during recovery after a failed scheduled run.
Monitoring COB Runs
Check the status of recent runs:
bash
curl https://your-instance.finecko.com/fineract-provider/api/v1/jobs/1/history \
-H "Fineract-Platform-TenantId: default" \
-H "Authorization: Basic $(echo -n 'mifos:password' | base64)"Each history entry includes:
| Field | Description |
|---|---|
status | SUCCESS or FAILED |
jobRunStartTime | When the run started |
jobRunEndTime | When the run completed (empty if still running) |
triggerType | SCHEDULER (scheduled) or APPLICATION (manual) |
errorLog | Stack trace if the run failed |
Check the m_loan_cob_error table in the tenant database for details on individual loan processing failures:
sql
SELECT loan_id, error_message, created_date
FROM m_loan_cob_error
ORDER BY created_date DESC
LIMIT 50;The Business Date API
Read the current business date:
bash
curl https://your-instance.finecko.com/fineract-provider/api/v1/businessdate \
-H "Fineract-Platform-TenantId: default" \
-H "Authorization: Basic $(echo -n 'mifos:password' | base64)"Manually advance the business date (use with care in production - this does not replay any COB steps):
bash
curl -X POST \
https://your-instance.finecko.com/fineract-provider/api/v1/businessdate \
-H "Fineract-Platform-TenantId: default" \
-H "Authorization: Basic $(echo -n 'mifos:password' | base64)" \
-H "Content-Type: application/json" \
-d '{
"date": "15 March 2024",
"dateFormat": "dd MMMM yyyy",
"locale": "en",
"type": "BUSINESS_DATE"
}'WARNING
Manually advancing the business date does not re-run COB steps for the skipped days. It only moves the date forward. Use this only for recovery or testing scenarios, and only after consulting with Finecko support on production instances.
Failure Recovery
If COB fails mid-run:
- Check
lastRunHistory.errorLogandm_loan_cob_errorfor the cause - Fix the underlying issue (data integrity error, broker connectivity, resource exhaustion)
- Unlock any loans that remain locked: Fineract automatically unlocks stale loan locks on the next COB run startup, but you can confirm via
SELECT * FROM m_loan WHERE locked_by_cob_job_id IS NOT NULL - Trigger a manual COB run to process the backlog
- Verify the business date advanced correctly after the successful run
If multiple days of COB have been missed (e.g. after an extended outage), COB must be run once per missed day, advancing the business date one day at a time, until it reaches the current date.
COB and the External Events Backlog
COB-triggered events (accruals, overdue charges) are written to m_external_event with status TO_BE_SENT the same as all other business events. If the Send Async Events job has also been stopped during an outage, these events will queue up and dispatch once the job resumes. No events are lost - they drain from the backlog when the broker connection is restored.
Monitor the event backlog alongside COB status:
sql
SELECT status, COUNT(*) FROM m_external_event GROUP BY status;A large TO_BE_SENT count alongside a failed COB run indicates both systems need attention.