Table of contents
Open Table of contents
what slice inherited
slice had acquired a legacy core-banking platform: Java 8, Play Framework, one big monolith. it worked, but it could only ever run as a single instance. the services inside it talked to each other through direct function calls and shared in-process imports, and every lock that guarded shared state was an in-memory lock. that combination is what pinned it to one box. you couldn’t bring up a second replica, because two replicas can’t share an in-memory lock, so a second copy would corrupt the very state the first one thought it was protecting. one instance, no horizontal scale, a hard ceiling on requests per second, and no real high availability.
the mandate was to fix exactly that: turn the single box into distributed services that scale independently. that meant two changes, repeated almost everywhere in the codebase. first, services had to stop reaching into each other’s code. direct calls and cross-module imports had to become API calls over the network, so any one service could be deployed and scaled on its own. second, in-memory locks had to become distributed locks in Redis, so any number of replicas could coordinate on shared state instead of each one trusting its own process memory.
my piece: the loan vertical
i led the loan-management side of this, with one teammate who had just joined. our job was to take the loan services out of the monolith’s assumptions and make them horizontally scalable, so they could run as multiple replicas and serve more traffic instead of bottlenecking on a single process.
concretely that was the same two moves, done carefully across the loan code: every inter-service touch point became a network API with a real request and response contract, and every in-memory lock that guarded loan state moved to a Redis lock so the replicas stayed consistent. the goal wasn’t a rewrite, it was to change how the pieces talk to each other without changing what they do.
the part i’m proud of: a tool that finds every seam
the slow, error-prone part of work like this is discovery. before you can convert a direct call into an API, you have to find every direct call that crosses a service boundary, and a monolith this size hides a lot of them. doing that by hand is weeks of grepping, and the ones you miss show up later as runtime errors.
so i built a tool to do the finding for us. it ran a breadth-first traversal over the IDE’s call graph, starting from a module’s entry points and walking outward, and flagged every invocation that crossed a service boundary, every direct call and cross-module import that would have to become an API. it wrote the results to a spreadsheet: each call site, where it lived, and the objects that were passed across the boundary. those objects mattered twice over, because they were exactly the request and response schemas we’d need to define for the new APIs.
a team ran it over their own repo from the terminal and got back the full, ordered list of places to change, plus the schemas they implied. that turned the migration from manual archaeology into one command, and it’s what made the work tractable for more than just my own vertical.
leading the cross-team rollout
this was never one team’s job. every vertical had to decouple its own services the same way, and roughly on the same timeline, because a half-decoupled system is worse than either end state: you pay the cost of network hops without yet being able to scale. so part of my role was coordinating across teams, getting them aligned on the approach and the schedule, handing them the call-graph tool to scope their own piece, and keeping the cutover moving so the parts landed together instead of drifting.
migrating the NPA jobs to a modern stack
alongside the decoupling, i moved the non-performing-asset daily jobs off the legacy stack onto the new microservice architecture, on Spring Boot and Java 21. NPA is the daily batch that classifies overdue loan accounts, so it’s core to a lending system, and getting it onto the modern stack was part of the same shift from one aging monolith toward services that the team could actually maintain and scale.
leading AI adoption on the team
i’d been using Cursor since very early, before most of the team had picked up AI tooling at all. so i led getting the team onto it, and set up the editor rules and configuration for our repo so the assistant followed our conventions and working style instead of fighting them. it was a small thing on paper, but it changed how fast the team could move through repetitive migration work.
stack and honest framing
Java 8 moving to Java 21, Play Framework on the way out, Spring Boot on the way in, Redis for distributed locks, and the call-graph tool that scoped the whole thing. roughly Dec 2024 to Jul 2025.
the headline isn’t a benchmark, it’s the shape of the problem: taking a single-instance monolith held together by in-memory locks and direct calls, and turning the loan vertical into services that scale on their own, then building the tooling and the cross-team coordination that let the rest of the system follow.