Building Apache Fineract custom modules

Extend Apache Fineract through its custom module structure, replaceable services and your own image build, so core upgrades remain possible. This page covers how the pieces fit, what to decide first and what the work costs in effort.

On this page

When Apache Fineract custom modules are the right way to extend the platform

A bank or lender often needs country or institution logic that the standard product configuration does not cover, such as a fee rule, a reporting extract or an end-of-day step. The wrong answer is to edit Fineract's core sources, because every later release then becomes a merge exercise against your own changes. A custom module is the alternative: a separate Java component that sits beside the core code, adds or replaces behavior, and is packaged into your own image. It is the right tool when the requirement changes how Fineract behaves inside the platform, not when it only needs data held outside it.

Configuration should come first. Loan and savings products, charges, accounting mappings and business rules are set through Fineract's own features, and an external system that only reads or writes records should use the REST API from outside. Build a module when the logic must run inside Fineract's transaction or batch processing, or when it must replace a service the platform exposes for that purpose. Before committing, confirm with the project's platform documentation which extension points exist in the release you plan to run, and list each customization against the component it touches.

Compatibility is decided by the release line and cadence. Fineract is in its 1.x line, its website lists 1.15.0 as the current stable release, and the README states that official releases are created quarterly, at the end of each quarter, with end-of-life releases archived. Plan to rebase onto each quarterly release or to pick a supported one deliberately, and ask who owns that work and how long a release stays supported. Fineract CN was deprecated and archived without an official release, so any module built against it would have no upstream path.

Which extension points a custom module can use

Six mechanisms sit between a plain configuration change and a full custom module. Some need code packaged into your image, while others are switched on or set through configuration. The sibling pages cover each mechanism in depth, so this grid only shows what each one touches and what to check first.

Custom folder code

Your module's source sits in the custom folder beside the core code, so Fineract's own sources stay unedited and an upgrade becomes a merge against a new release. Everything in this folder is compiled into the artifact you package into your image, so a module change is a build and deploy event that moves through your pipeline. Check that the module builds against the exact release you target and that the build is pinned in your pipeline.

Replaceable services

Fineract is built on Spring, and a bean you provide in your module can take the place of the default implementation for that service. This is the cleanest way to change behavior without editing core files, but it only works for services the project exposes as supported seams. Before you override one, confirm in the source for your target release that the service is designed for replacement, and list every override so the upgrade review can test each one.

Scheduled batch jobs

A custom job is a Spring Batch job with its tasklets, registered in the database by a Liquibase migration and given a name that configuration references. Migrations run only on write instances, so the job appears only after the write instance has applied the change. Check the job's schedule, its failure and restart behavior, and whether it can run on the batch instance without contending with Close of Business.

Close of Business steps

Close of Business is the end-of-day Spring Batch job for loans, and custom steps run inside the end-of-day job to add institution-specific logic. Step order matters because later steps read what earlier ones posted. The job can run for several hours on deployments with millions of accounts, so plan each step's runtime against that window, test it at production volume, and confirm how it behaves when the job is partitioned across worker nodes.

Transaction processors

A transaction processor decides how a repayment is allocated across the components of a loan. Replacing the processor factory changes that decision for every product that uses it, so a small allocation error can reach many loans at once. Check each affected product against existing loan schedules and past repayments in a non-production tenant before release, and keep a record of which products the change covers.

Business events

Fineract can publish business events, such as ClientCreated, to ActiveMQ or other JMS brokers and to Apache Kafka. Publishing is disabled by default, so switching it on is a configuration change, while new event classes and their Avro schemas need module code. Confirm the broker, topic naming and consumer contracts before enabling, because downstream systems will depend on the shape of each event.

How the custom folder convention and starter auto-configuration fit together

A custom module lives in its own folder beside the upstream Fineract source, so your code can be versioned, reviewed and upgraded separately from the core project. The convention keeps your classes in packages your team owns, which makes it clear during code review what comes from upstream and what your team wrote. The layout also determines how the build compiles your module against a specific Fineract release, so the folder structure is a design decision, not only a file arrangement.

  • Separate repository or folder: Keep custom code in a repository your team controls, and pull in the upstream Fineract source at a pinned release tag.
  • Feature boundaries: Group each feature, such as one business step or one processor, into its own package so changes and tests stay contained.
  • Build descriptor: Each module declares the Fineract artifacts it compiles against, so version mismatches should surface at build time rather than at runtime.
  • Database change scripts: Keep schema and data changes in a dedicated resources path that Liquibase reads, separate from the upstream changelogs.
  • Package naming and ownership: Agree package prefixes and named owners early, so custom code is easy to locate during an upgrade.

Confirm the exact folder names, build layout and sample structure in the custom folder documentation and examples in the apache/fineract repository for your target release before creating the structure.

Module packaging and override options and when each fits

This table compares the main ways to package custom code and change Fineract behavior, from one module inside the build to a derived image with separate modules. Read each row for the upgrade risk and the verification effort it adds, then match the option to how many changes you need and how often you expect to upgrade. The main takeaway is that the smaller and more isolated the change, the cheaper it is to carry across upgrades.

OptionUpgrade riskVerification effortWhen it fits
One custom module in the buildModerate: the module must track core interfaces and bean names across each releaseOne regression suite covering every custom behavior after each rebuildA small set of related changes owned by one team
Separate modules per concernModerate: each module can break on its own, but changes stay easier to locatePer-module tests plus an integration run for cross-module interactionsSeveral business areas such as custom jobs, events and processors
Overriding a replaceable serviceHigher: replaced services can drift from upstream logic you copiedFull behavior tests for the replaced service and its callersOnly when the default behavior must change and no extension point exists
Adding a new beanLower: new beans leave existing service logic untouchedTests for the new bean and its wiring, plus a smoke test of core flowsAdditive features such as extra endpoints, jobs or processors
Official image with modules added at buildDepends on how often you rebuild against new core releasesImage build checks and a regression run on each new buildTeams that build and test their own image in a controlled pipeline
Derived image on top of the official imageLower: the base image stays unchanged and you control only your layerVerify the base image version and your layer together on each upgradeTeams that want a clear separation between upstream and local code
Editing core sources directlyHighest: every upstream merge becomes a manual conflict resolutionFull regression of the forked codebase after each mergeGenerally avoid; confirm with your architect before accepting this path

Setting up a custom module without touching core sources

This sequence takes a custom module from a confirmed target release to a regression-tested image running on a staging tenant. Each step closes with a check, so a fault is found where it was introduced and not during the final regression run.

  1. Confirm the target release

    The architect and lead developer pin the Fineract release the module will target. The project website lists 1.15.0 as the current stable release, and the documentation for the chosen version defines which extension points are supported. Record the extension points you plan to use and any you have ruled out, and confirm each one against the documentation for that exact release. Official releases are created quarterly, so note the release cadence in the plan. This step is done when the written note is approved and the team agrees that no core source file needs to change.

  2. Create the custom module

    A developer creates the module in the team's agreed layout, following the custom folder convention described in the project documentation. The build file should depend only on the released Fineract artifacts for the pinned version, with no copies of core classes. Confirm the exact folder names and build settings in the documentation and the repository before scaffolding. The check is that the module compiles from a clean checkout and that the diff against the upstream release touches nothing under the core source tree.

  3. Add an auto-configured service override

    The developer registers a Spring bean that replaces one default service, using the auto-configuration mechanism the documentation describes for that release. Keep the override narrow, with one replaced service per change, and document the reason next to the code. Confirm in the release documentation that the service is replaceable before writing any code. The check is that, on a local write instance, the startup log shows the custom bean active and the default bean is not used, and that a unit test asserts the override's behavior.

  4. Write migrations and tests

    The developer adds Liquibase changesets to the module for any new tables, registrations or configuration rows the feature needs, and each changeset is versioned and has a clear identifier. Liquibase migrations run only on write instances, and on Kubernetes they can run separately with the liquibase-only Spring profile, so plan the run order in the deployment. Add integration tests that run against a running backend. The check is a clean Liquibase run on an empty PostgreSQL database, followed by a second run that reports no pending changes.

  5. Build the image with the module

    The release engineer adds the module to the Fineract image build used for the deployment. The project recommends PostgreSQL, and the official Docker image ships only the PostgreSQL JDBC driver, so confirm that no other database driver is needed. Tag the image with both the Fineract release and the module version, and record the image digest in the release record. The check is that the image starts, the module appears on the classpath, and the image digest matches the one recorded in the release.

  6. Deploy to a staging tenant

    The platform team deploys the image to a staging environment that mirrors production topology, with a write instance, a batch instance and read instances on database read replicas, as the project's documentation describes for high load. Requests for the test tenant carry the Fineract-Platform-TenantId header. The check is that the module's endpoints respond on the write instance, the read instances return the expected data after replication, and the batch instance runs the scheduled work without errors.

  7. Run regression and record results

    The QA lead runs the regression suite against the staging tenant, covering core loan, savings and accounting flows alongside the custom behavior. Use business date control so results are repeatable, and schedule the Close of Business Day job with its run time in mind, because it can run for several hours on deployments with millions of accounts. Compare results with a baseline from the unmodified release. The step is complete when results are signed off and recorded, and any failure blocks promotion to production.

Keeping custom modules compatible across Fineract upgrades

Compatibility across upgrades is decided at the start, by pinning the Fineract release your custom modules are built and tested against and by keeping every override as narrow as possible. Pin the exact release in your build, for example the version your Gradle or Maven build resolves, and record it in your configuration baseline so that no image is built against a moving target. Fineract's website lists 1.15.0 as the current stable release, but the release you adopt should be the one your support arrangement covers, not simply the newest. Narrow overrides matter because every replaced service, overridden bean or custom processor is a point where a core change can break your code silently. Prefer additive extension points, such as new modules and beans that do not replace core classes, and treat each replacement as a documented exception with an owner.

Plan upgrades around the project's release cadence and end-of-life policy. The Fineract README states that official releases are created quarterly, at the end of each quarter, under the ASF release policy, and that end-of-life releases are archived. That policy sets your timeline: once a release line reaches end of life, you need a tested path to a supported one. Track the release calendar and the archive notices, and ask your support provider or internal team to confirm in writing which release line the support plan covers and for how long. Do not assume that a quarterly release is a safe drop-in; each candidate should be treated as a new build.

Run regression on every upgrade candidate before adopting it, in a non-production environment that mirrors your production tenant configuration, extensions and database version. Build your custom modules against the candidate, run your automated regression suite and your business scenarios for loans, savings, accounting and end-of-day processing, and review any startup warnings about overridden beans or changed Liquibase changesets. Include your custom database migrations in the test, since they run only on write instances and can conflict with new core migrations. Record the results, the candidate version and the image digest in your change record so that the adoption decision and any rollback point are traceable.

Security and operational risks of custom code in a core banking system

Custom modules run inside the same process, database and release pipeline as the core ledger, so a defect or misconfiguration in your code can reach balances, credentials and batch jobs. The cards below cover the controls to set before the first custom module reaches a shared environment.

Limit custom code database access

A custom module that runs in the Fineract process inherits the database connection the core uses, which means it can read and write client, loan and accounting tables. Decide which schemas and tables custom code may touch, create a separate database role for any custom migration, and review every migration that alters core tables before it is merged. Ask your implementation partner which tables each module reads and writes, and test that the role cannot change ledger tables it does not need.

Replace default credentials

The README shows a development login of mifos with a password that must be changed for any real deployment. Remove the default account from every environment that holds real data, rotate database and tenant credentials, and store them in a secrets manager rather than in image layers or environment files committed to source control. Confirm that custom modules do not add their own service accounts with shared passwords.

Enforce HTTPS only

The Fineract documentation strongly encourages HTTPS only and documents SSL settings through environment variables such as FINERACT_SERVER_SSL_ENABLED. Set this variable in every environment, including the ones custom modules are tested in, so that test habits match production. Check that any custom endpoint or callback URL your module exposes or calls also uses TLS, and that the gateway or load balancer does not downgrade connections to plain HTTP before they reach the application.

Control jobs on the batch instance

Custom batch jobs and business steps run on the batch instance, where the Close of Business Day job can run for several hours on large deployments. A faulty job can hold locks, slow end-of-day processing or leave partial results. Confirm which instance type runs each job, set timeouts and restart behavior, and rehearse failure and rerun on a copy of production data before a release. Agree who can start, stop and rerun jobs in production.

Keep event publishing deliberate

Fineract can publish business events such as ClientCreated to ActiveMQ, other JMS brokers or Apache Kafka, but this is disabled by default. Enable publishing only for the event types and broker destinations that a named consumer needs, and document who owns each consumer. Custom event classes can expose personal or financial fields to downstream systems, so review each payload against your data classification before turning it on.

Control image and release changes

Custom modules ship inside your container image, so every change to custom code is also a change to the platform build. Pin the Fineract base version, build images from tagged source in version control, and require peer review and a tested rollback image for each release. Confirm the Fineract release line you target is still supported under the project's quarterly release and end-of-life policy before you freeze the build.

Questions buyers ask before building Fineract custom modules

How much effort does a Fineract custom module take, and what drives it?

Effort depends on what the module touches, not on how many lines of code it has. A module that adds a new endpoint with its own data is different from one that changes how an existing business step or event behaves. The main drivers are:

  • The Fineract extension points involved, such as a batch job, a business step, a processor or a business event
  • Whether the module needs its own database migration and how many tables it adds
  • How much of the accounting, loan or savings logic the rules depend on, and how many products they apply to
  • How many test cases are needed to prove the change does not alter standard behavior

Ask any provider for a written scope listing each extension point, its inputs and outputs, and the test evidence expected before sign-off. A quote without that breakdown cannot be compared.

Can our in-house team build Fineract custom modules?

A team with strong Java and Spring experience can build modules, but it also needs working knowledge of Fineract's database schema, Liquibase migrations, the Close of Business job and the tenant model. The Fineract README notes that a production deployment can be complex and time-consuming and that organizations may pay a vendor for deployment and maintenance, so plan for that cost in the decision. Check your team against four questions: who has run a Fineract instance in production, who can write and maintain a regression suite, who will own upgrades, and who can review changes to accounting logic. Gaps in any of these are usually where internal builds slow down.

What does the work need from our side: environments, sample data and sign-off?

Most of the delay in custom work comes from client inputs, not from coding. Before build starts, you should be able to provide:

  • A non-production environment that matches your production PostgreSQL version, tenant setup and configuration, with a separate tenant for testing
  • Anonymized or synthetic sample data covering the loan and savings products the module will affect, including edge cases such as early repayments and arrears
  • A written business rule for each behavior, approved by the people who own accounting and credit decisions
  • A named approver who signs off test results and the release

Agree these in the written scope, because each missing input moves the timeline.

How long does it take to validate a custom module after a Fineract upgrade?

Validation time depends on how many custom extension points you use, how closely they depend on internal Fineract classes, and how complete your regression suite is. The Fineract project publishes quarterly releases at the end of each quarter, so you should plan a recurring validation cycle rather than a one-off. Modules that use stable, documented extension points and have automated end-to-end tests are quicker to check than modules that override core services. Ask for the upgrade validation steps, the test coverage per module and the release notes you will review before any upgrade is scheduled. Confirm the expected effort in writing before you commit to a retainer.

Does custom code void Fineract support or the community's support?

Custom code does not automatically void support from the Apache Fineract project, which is licensed under the Apache License 2.0, but it does change who is responsible for the behavior of your system. Any problem you see after an upgrade may be caused by your module, so you need to be able to reproduce it on a clean Fineract build. Keep modules separate from core sources, document every extension point used, and record which Fineract release each module was tested against. Vendor support contracts vary, so confirm with your provider exactly which issues they will handle and how they separate custom code faults from platform faults.

When should we configure Fineract instead of writing a custom module?

Choose configuration when the behavior you need is already offered by Fineract's configurable products, business rules and settings, because configuration is covered by the project's normal upgrade path. Choose a module only when a documented requirement cannot be met by configuration, such as a country-specific end-of-day rule or a repayment allocation that the product settings cannot express. Before writing code, list each requirement and mark whether configuration meets it, then review the gaps with your credit, finance and compliance owners. A module that replaces a configuration option usually adds more upgrade and test work than it saves.

What risks should we check before approving a custom Fineract module?

Custom modules carry three main risks: changes to core behavior that are not visible until a batch job runs, dependence on internal classes that change between releases, and data changes made through migrations that are hard to reverse. Ask for a rollback plan, a test run of the Close of Business job against realistic data volumes, and a record of every database migration the module adds. The Fineract documentation notes that Liquibase migrations run only on write instances, so confirm how your deployment will apply them. Also check how access to module configuration and secrets is controlled, since custom code runs with the same privileges as the platform.

How DeployOpen scopes a custom module engagement

Custom module engagements are fixed-scope: the scope, deliverables and timeline are agreed in writing before any code is written. The scope document should name each extension point the module uses, such as a custom folder module with its own Spring configuration, a replaceable service bean, a Spring Batch job, a business step or an event serializer, and state why each one is needed. It should also name the target Fineract 1.x release the module is built against, the test plan that will prove the module works on that release, and the image build that will package the module with the Fineract war or container. Ask for each of these to be listed explicitly. A scope that says only "customize Fineract" gives you nothing to test against or price against.

The scope should also record the decisions that most affect future cost: which core classes the module will override and why, which database changes it will register through Liquibase and whether they are reversible, and which configuration keys operators will need to set. Confirm with your own team which Fineract version you will run in production and whether a later upgrade is already planned, because a module tied to one release line needs compatibility work each time that line moves. The Apache Fineract documentation is the reference to check extension details against before the scope is signed.

At handover, you should receive the module source in your repository, the build and image definitions, the automated tests, a runbook covering configuration and rollback, and a short compatibility note listing the Fineract release tested. Ongoing upgrades, compatibility checks and continuous testing of the module are available as retainers, which you can scope separately once the handover is complete. Ask for these retainer terms in writing so the boundary between fixed-scope delivery and recurring support is clear before go-live.

How DeployOpen builds and supports Fineract custom modules

DeployOpen plans, builds and supports custom Fineract modules inside your own infrastructure, keeping extensions outside the core sources so the platform stays upgradeable. Engagements are fixed-scope, with the scope, deliverables and timelines agreed in writing before work starts.

How we scope

  • Written scope lists each custom module, the Fineract extension point it uses and the business rule it implements.
  • Scope names the target Fineract release line and the upgrade approach agreed before any code is written.
  • Scope records the environments, database platform and deployment model where the module will run.

How we build

  • Modules are built in a custom folder structure, so the Fineract core sources remain unmodified.
  • Replaceable services and auto-configured components are used wherever Fineract provides them, instead of editing core classes.
  • Each module is packaged into the deployment image with versioned build artifacts that your team can reproduce.

How we hand over

  • Handover includes source code, build instructions, configuration keys and a record of every extension point used.
  • Automated tests covering each module run in your pipeline, with results and test data documented for your team.
  • Operating notes explain how to check module compatibility before applying each new Fineract release.

How we support

  • Ongoing support, upgrades and continuous testing are available as a retainer after handover.
  • Support covers reviewing each new Fineract release against your custom modules before you schedule an upgrade.
  • Retainer scope, response expectations and upgrade work are confirmed in writing before the retainer starts.

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.