Ever inherited a system where the customer's name lives in a field called CUST_NM_1, dates are stored as eight-digit strings, and "deleted" really means a status flag set to 9? Now imagine building a clean, modern service that has to talk to that thing every day. Without care, its weirdness seeps into your beautiful new code until your model looks just as crusty as the old one.
The Anti-Corruption Layer is the firewall against that seepage. It's a dedicated translation boundary that lets your new system stay clean while still cooperating with the messy old one.
The problem
When two systems with different models talk directly, the stronger model usually wins — and it's rarely the one you want. If your new service calls the legacy system and uses its data shapes directly, those shapes spread through your codebase: the cryptic field names, the magic numbers, the assumptions baked in decades ago.
This is coupling at its worst. Your new system becomes dependent on details it shouldn't know about, and you lose the ability to evolve independently. The moment the legacy system changes, your clean code breaks — and over time, the corruption is complete: there's no clean model left to protect.
- New ServiceTalking to the legacy system directly, it absorbs the old model's cryptic fields and magic numbers.
- Legacy SystemThe stronger, messier model wins — its quirks spread straight into the new code.
How it works
An anti-corruption layer sits between the two systems as a dedicated translator. Your new service never speaks to the legacy system directly; it speaks to the ACL in its own clean vocabulary. The ACL then translates that request into whatever the legacy system expects, calls it, and translates the response back into your model on the way home.
Inside, the ACL is doing abstraction work: mapping customer.name to CUST_NM_1, parsing those eight-digit dates, turning status 9 back into a proper "deleted" flag. Your code stays blissfully unaware of all of it. The diagram below shows a clean request from the new service passing through the ACL, which converts it before reaching the legacy system — and converts the answer back on return.
- New ServiceYour clean, modern system. It speaks only its own well-designed model.
- Anti-Corruption LayerA translator that maps between the two models so neither side's quirks leak across.
- Legacy SystemThe old or external system with cryptic fields and odd data shapes, kept quarantined.
Keep the ugliness in one place. The whole point is that all the gnarly mapping logic lives inside the ACL and nowhere else. If translation rules start leaking into your domain code, the layer isn't doing its job — tighten the boundary.
When to use it
An ACL shines during incremental migrations. You're carving a microservice out of a monolith, or replacing a legacy platform piece by piece, and the two must coexist for a while. The ACL quarantines the old system so the new one can grow cleanly — and when the legacy system finally retires, you delete the layer and your model is left pristine. It pairs well with the strangler-fig approach to gradual replacement.
It's also the right tool whenever you integrate with an external third-party API whose model you don't control: wrap it in an ACL so vendor quirks never become your problem. The trade-off is a real one — it's another component to build, test, and maintain, and it adds a little latency per call. For two systems that already share a clean, stable model, an ACL is just overhead you don't need.