Migrating Apache Fineract from MySQL or MariaDB to PostgreSQL: the gotchas
6/15/2026

If you are running Fineract on MySQL or MariaDB, the ground has shifted under you. The project has voted to standardize on PostgreSQL and formally deprecate the other two, and it has been unusually direct about what that means for you: it will not be providing migration tooling, guides, or scripts. You are expected to get yourself across, on your own.
So this migration is going to happen, sooner or later, and the question is just whether you do it deliberately or under pressure. We run managed Fineract on PostgreSQL, and we have moved data off the old engines, so here is the honest version of what the move involves. The good news is that the part everyone worries about, swapping the database engine, is largely a solved problem. The bad news is that the part nobody budgets for is the part that actually bites.
Why the project is doing this
For most of its life Fineract supported MySQL, MariaDB, and PostgreSQL, and supporting three SQL dialects is a quiet, constant tax. Every query has to work on all of them, every feature has to be tested on all of them, and the project cannot lean on anything one database does well because the others might not. Standardizing on a single engine makes development faster and the system more predictable, and PostgreSQL is the one they chose, for its sequences, partitioning, indexing, and transactional behavior.
The decision is real and it is committed. The README now lists PostgreSQL as the required database and says support for the others is deprecated. The build the project leads with runs against PostgreSQL, and new fixes and features are written and tested on Postgres first. It is the canonical target now, and the old engines are along for the ride, not driving. And the project has explicitly declined to own your migration: best-effort support only, with guides and scripts left to users, vendors, and anyone willing to sponsor the work.
One nuance is worth knowing, because it cuts the other way. Deprecated does not yet mean removed: the code still runs on MySQL and MariaDB, and will for a while yet. But the defaults and the documentation are being moved over to PostgreSQL, and the JDBC drivers for the old engines are no longer shipped with Fineract, because of license incompatibilities, so you download and supply them yourself. Do not read "it still works" as a reason to stay. The engines that are merely tolerated today are the ones that get dropped tomorrow, and the direction of travel is not subtle.
The easy part: the engine swap itself
Here is the reassuring half. Fineract is genuinely good at moving its own schema across, because it was built to run on more than one database.
Fineract's own schema is managed by Liquibase, which can build the canonical PostgreSQL version of every table from scratch. Its built-in queries go through an internal translation layer that already knows the difference between MySQL and PostgreSQL: the backticks, the LIMIT syntax, the date functions, the auto-increment-versus-sequence distinction. You do not have to touch any of that. And for the data itself, the open-source tool pgloader does the heavy lifting in a single pass, copying the rows and converting the types as it goes, mapping MySQL's tinyint to a real boolean and, importantly, turning MySQL's invalid 0000-00-00 zero-dates into proper nulls that PostgreSQL will accept.
If your Fineract instance were nothing but the stock schema and the stock features, the migration would be close to mechanical: stand up PostgreSQL, run pgloader, reset the sequences, swap a handful of environment variables, and start up. Our migration guide walks through exactly that, step by step. The trouble is that almost nobody's instance is just the stock schema.
The hard part: everything you added yourself
The engine knows how to translate its own queries. It knows nothing about yours. Two features in particular sit entirely outside that safety net, and between them they account for most of the pain of a real migration.
Your reports. Fineract reports are stored as raw, hand-written SQL in a database table, and when a report runs, that SQL is executed almost exactly as written. Fineract substitutes a few parameters and checks it for injection, but it does not translate the dialect of the query for you. So every MySQL-ism in a report is a live grenade: a backtick-quoted column, an IFNULL, a GROUP_CONCAT, a WEEK() or MONTHNAME() date function, any of them throws a SQL grammar error the moment the report runs on PostgreSQL. This is not hypothetical; some of Fineract's own bundled reports have this exact bug and break on Postgres. Whatever custom reports your institution has built over the years, every one of them written against MySQL needs to be found, rewritten in PostgreSQL syntax, and tested.
Your datatables. Datatables, the custom tables you attach to clients, loans, and the rest, are created at runtime through the API, which means they live outside Liquibase entirely. Liquibase migrates Fineract's schema, not yours, so it has no idea your datatables exist. Their structure and their data have to be moved by hand. On top of that there is a case-sensitivity trap waiting: MySQL is lenient about identifier case, PostgreSQL is not, and because Fineract quotes its identifiers, a datatable created as MyTable becomes a case-sensitive "MyTable" on Postgres. Any report or query that referred to it loosely, relying on MySQL's forgiving behavior, will suddenly fail to find it.
The pattern is the same in both cases. The things Fineract owns, it migrates. The things you authored, you migrate, and they fail loudly the first time they run if you have not.
The smaller traps
A handful of other things will catch you, none of them hard once you know to look:
- Reset the sequences. After pgloader copies your data, PostgreSQL's sequences do not know about the IDs already in the tables and will start handing out numbers from one, which collides with existing rows the moment you insert anything. You reset every sequence to its table's current maximum after the load.
- The migration history table. The Liquibase changelog carries a context that says it was applied on MySQL, which leaves the Postgres-only changesets looking unapplied. The clean fix is to drop the changelog tables and let Fineract rebuild them, recognizing the existing schema as it goes.
- Both databases. Fineract keeps a tenant registry database alongside each tenant's own database. You migrate all of them, and then you re-point the registry so it knows where the tenant databases now live.
- Two small config things. PostgreSQL 15 and later no longer let any user create tables in the public schema by default, so you grant that explicitly or Liquibase cannot build the schema. And drop the
serverTimezoneparameter from your JDBC URL, because the PostgreSQL driver ignores it; set the timezone on the server instead.
How to do it without losing anything
The shape of a safe migration is straightforward. Do it onto a fresh PostgreSQL server running alongside your live MySQL one, never in place, so your rollback path is simply to not cut over. Migrate the tenant registry and every tenant database. Run pgloader, reset the sequences, rebuild the Liquibase history, re-point the registry, and swap the driver class and JDBC URL in your configuration. Then, and this is where the real time goes, exhaustively test every datatable and every report before you let any traffic near it. The engine swap you can validate in an afternoon. The custom surface is what you budget days for.
So, what should you do?
Do not wait for this to become urgent. The longer you stay on the deprecated engine, the more custom reports and datatables you accumulate, and the more you have to rewrite when you finally move, all while running further and further behind a project that is now developing and testing on PostgreSQL first. The migration is not going to get easier, and nobody upstream is coming to do it for you. The deep configuration and the full step-by-step are in our migration guide; treat the engine swap as the routine part and your reports and datatables as the actual project. It pairs with the first-boot guide, since the two-database setup and the driver swap are exactly where fresh instances trip, and with the cost breakdown, since this forced migration is one more line in the upgrade tax.
That is the part we take off your plate. Finecko runs managed Apache Fineract on PostgreSQL out of the box, and when you move to us we migrate your existing data during onboarding, datatables and reports included, so you are not standing up pgloader and rewriting SQL on a deadline. If you would rather run it yourself, the rule is simple: the engine will move itself, your custom SQL will not, so point your attention there and migrate before the choice is made for you.
Skip the ops. Run managed Apache Fineract.
Finecko runs managed Apache Fineract for you - the Finecko Hub, the right topology, connection pooling, backups, TLS, patching, and on-call. You get the open-source core without the operations, and the free plan is a full environment to try with no credit card.