Cosmic Module

O

Qubits of DPK

March 20, 2026

Core Open Source

The Analogy

Imagine a single large office building with 100 companies renting floors. Each company has its own employees, furniture, filing cabinets, and confidential data. They share the building (electricity, plumbing, security guard at the door) but they NEVER see each other's data.
Apache Fineract is that office building. Multiple banks (tenants) share ONE running Java application, but each bank's data is completely isolated.

Why Multi-Tenancy?

If you're a SaaS company offering banking software:
  • Running 100 separate Fineract instances = 100x infrastructure cost
  • One multi-tenant Fineract instance = 1x infrastructure cost, 100 banks
This is exactly how cloud services like Salesforce, Shopify work.

How Fineract Implements It — Schema-Based Isolation

Fineract uses schema-per-tenant isolation. Each bank gets its own MySQL/PostgreSQL schema (like its own separate filing room).
javascript
QUBITS OF DPK
1MySQL Server
2├── fineract_tenants       ← Global registry of all tenants
3├── fineract_default       ← Data for "default" tenant (demo bank)
4├── fineract_bank_kenya    ← Data for a Kenya bank
5├── fineract_bank_india    ← Data for an India bank
6└── fineract_mfi_uganda    ← Data for Uganda MFI
Each schema has ALL the same tables (m_client, m_loan, m_savings_account etc.) but completely separate data.

The Tenant ID Header — The Security Guard

Every HTTP request to Fineract MUST include:
javascript
QUBITS OF DPK
1Fineract-Platform-TenantId: default
This is how Fineract knows which bank's schema to use. Missing this header → 400 Bad Request.
java
QUBITS OF DPK
1// TenantAwareBasicAuthenticationFilter reads the header
2String tenantIdentifier = request.getHeader("Fineract-Platform-TenantId");
3if (tenantIdentifier == null) {
4    throw new InvalidTenantIdentifierException("No tenant ID provided");
5}
6// Sets the tenant context for this thread
7ThreadLocalContextUtil.setTenant(tenant);

ThreadLocal — How the Tenant Context Travels

Layman Explanation

In a call centre, every agent has their own headset. When Agent A takes a call from Bank Kenya and Agent B takes a call from Bank India, they don't mix up the conversations. Each agent's headset carries only THEIR call.
In Java, ThreadLocal is that personal headset. Each HTTP request runs on its own thread. The tenant context is stored in a ThreadLocal — only visible to that thread.
java
QUBITS OF DPK
1// ThreadLocalContextUtil stores tenant for current thread
2public class ThreadLocalContextUtil {
3    private static final ThreadLocal<FineractPlatformTenant> TENANT_CONTEXT
4        = new ThreadLocal<>();
5
6    public static void setTenant(FineractPlatformTenant tenant) {
7        TENANT_CONTEXT.set(tenant);
8    }
9
10    public static FineractPlatformTenant getTenant() {
11        return TENANT_CONTEXT.get();
12    }
13}

DataSourcePerTenantService — The Magic Switch

This is the core of multi-tenancy. Every time a DB query runs, Fineract:
  1. #
    Reads the current tenant from ThreadLocal
  2. #
    Gets that tenant's database connection
  3. #
    Runs the query against that tenant's schema
  4. #
    Returns the result
java
QUBITS OF DPK
1@Component
2public class DataSourcePerTenantServiceImpl implements DataSourcePerTenantService {
3
4    // Connection pool per tenant
5    private final Map<String, DataSource> tenantToDataSourceMap = new ConcurrentHashMap<>();
6
7    public DataSource retrieveDataSource() {
8        FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
9        String tenantId = tenant.getTenantIdentifier();
10
11        // Create connection pool for this tenant if doesn't exist
12        return tenantToDataSourceMap.computeIfAbsent(tenantId,
13            id -> createDataSourceForTenant(tenant));
14    }
15}

The Tenant Registry — fineract_tenants Schema

The global fineract_tenants database (separate from all tenant schemas) stores:
sql
QUBITS OF DPK
1SELECT * FROM fineract_tenants.tenants;
2-- id | identifier        | name              | schema_name          | schema_server
3-- 1  | default           | Default Tenant    | fineract_default     | localhost
4-- 2  | bank_kenya        | Kenya Bank        | fineract_bank_kenya  | localhost
When a request comes in with Fineract-Platform-TenantId: bank_kenya, Fineract:
  1. #
    Queries fineract_tenants.tenants to find the record for bank_kenya
  2. #
    Gets the schema name: fineract_bank_kenya
  3. #
    Sets up a DB connection to fineract_bank_kenya
  4. #
    All subsequent queries in that request use fineract_bank_kenya

Complete Request Flow with Multi-Tenancy

javascript
QUBITS OF DPK
1HTTP Request:
2  GET /fineract-provider/api/v1/clients
3  Header: Fineract-Platform-TenantId: bank_kenya
4  Header: Authorization: Basic bWlmb3M6cGFzc3dvcmQ=
5
678  TenantAwareBasicAuthenticationFilter
9Reads "bank_kenya" from header
10Queries fineract_tenants DB → finds schema "fineract_bank_kenya"
11Sets ThreadLocalContextUtil.tenant = bank_kenya tenant object
12Authenticates user against fineract_bank_kenya.m_appuser table
13
141516  ClientApiResource.retrieveAll()
17Calls ClientReadPlatformService
18
192021  ClientReadPlatformServiceImpl
22Executes: SELECT * FROM m_client
23Spring routes this to DataSourcePerTenantService
24DataSource for "bank_kenya" is selected automatically
25Query runs against fineract_bank_kenya.m_client
26Returns only THAT bank's clients
27
282930  Response: [{clientId: 1, name: "John Kenya", ...}]

Liquibase — Schema Migration Per Tenant

When a new column is added to m_loan, it needs to be added to EVERY tenant's schema. Liquibase handles this.
javascript
QUBITS OF DPK
1fineract-provider/src/main/resources/db/changelog/
2├── tenant/
3│   └── parts/
4│       ├── 0001_create_m_client.xml
5│       ├── 0002_create_m_loan.xml
6│       └── 0003_add_external_id_to_m_client.xml  ← ran against ALL tenants
7└── tenant-store/
8    └── parts/
9        └── 0001_create_tenants_table.xml          ← only run once on fineract_tenants
On startup, Fineract runs all pending migrations against every registered tenant's schema.

Mentor Cheat Sheet