A common challenge when building APIs is supporting multiple live versions without letting the system turn into an unmaintainable mess. You need to keep older versions running for existing clients, roll out new versions safely, and avoid breaking changes that might take down production. And you need to do all that without duplicating too much infrastructure or introducing spaghetti logic inside your code. There’s no official pattern for versioning APIs in API Gateway + Lambda. API Gateway gives you stages and custom domains; Lambda gives you versions and aliases. But it’s up to you to stitch them together into a strategy that works. In this post, I’ll walk through several practical approaches to API versioning in this setup, explain the trade-offs of each, and help you choose the one that best fits your needs. Prefer duplicating Lambda functions over versionsA common question is: “Should I duplicate the Lambda function, or use Lambda versions and aliases?” I always prefer duplicating the function. Here’s why: 1. IAM roles don’t version. If multiple function versions reference the same IAM role, you can easily break the old versions with permission changes for the new version. With most IaC tools, duplicating the function and its IAM role is straightforward. And thanks to pay-per-use pricing, there’s no penalty for this unless you’re using provisioned concurrency. Technically, you can point different versions to different IAM roles, but this adds complexity, and most IaC tools don’t handle it well out of the box. Option 1: URL-based VersioningA very common approach is to add a version to the URL, e.g. You can do this using API Gateway stages or custom domain mappings. But I strongly recommend against using stages for versioning. Like Lambda versions, they add hidden complexity and are awkward to manage in IaC. Instead, duplicate the API Gateway, Lambda functions and IAM roles and use custom domains or path mappings to route traffic to each version. It’s more explicit and gives you better separation. Pros
Cons
Option 2: Custom HTTP header/query paramInstead of changing the URL, you can version via a custom HTTP header (e.g. This works well if you want to default everyone to a specific version but give early adopters a way to opt into a newer one. However, since multiple versions share the same route, API Gateway’s built-in metrics are no longer enough. You’ll need to emit custom metrics (e.g. using EMF) for fine-grained metrics & alerts. Pros
Cons
Option 3: LambdalithA Lambdalith is a monolithic application deployed as a single Lambda function, often using the Lambda Web Adapter [1]. It supports both URL and header/query param-based versioning. All routing happens inside your code – no duplicated infrastructure, but more internal complexity. For a deeper dive into Lambdaliths, check out this earlier article [2]. Here, I’ll just focus on versioning implications. Pros
Cons
Option 4: No breaking changes!Honestly, most versioning strategies suck… and I often find myself coming back to a simpler rule: just don’t introduce breaking changes. It’s the same advice I gave for versioning events in event-driven architectures [3]. No breaking changes, no need for versioning. Never remove/rename existing fields in your API contract, and never change the data type of existing fields. Instead, always add new fields. Pros
Cons
How to choose the right approachTo pick the right strategy, ask yourself:
For example, if v2 is a major rewrite and you need to support v1 long-term, separate APIs with custom domains may be worth the extra infra. But if v2 is mostly compatible, routing by header inside a single Lambda might be simpler. ConclusionAs much as I prefer avoiding breaking changes, sometimes you just can’t. So it’s worth thinking ahead about how you’ll handle versioning – even if you hope not to use it. There’s no one-size-fits-all answer. Use the questions above to weigh your options and pick the strategy that fits your product, team, and constraints. Links[1] Lambda Web Adapter [2] The pros and cons of Lambdaliths [3] Event versioning strategies for event-driven architectures [4] Pact, for consumer-driven contract testing |
Join 16K readers and level up you AWS game with just 5 mins a week.
I recently shared six event versioning strategies for event-driven architectures [1]. In response to this, Marty Pitt reached out and showed me how Orbital [2] and Taxi [3] use semantic tags to eliminate schema coupling in event-driven architectures and simplify the schema management. It's a novel way to manage schema evolution, and I want to share what I learnt with you. Problems with Schema Coupling In an event-driven architecture, event consumers are typically coupled to the schema of the...
Last week, we looked at 6 ways to version event schemas [1] and found the best solution is to avoid breaking changes and minimise the need for versioning. But how exactly do you do that? How can you prevent accidental breaking changes from creeping in? You can detect and stop breaking changes: At runtime, when the events are ingested; During development, when schema changes are made; Or a combination of both! Here are three approaches you should consider. 1. Consumer-Driven Contracts In...
Synchronous API integrations create temporal coupling [1] between two services based on their respective availability. This is a tighter form of coupling and often necessitates techniques such as retries, exponential delay and fallbacks to compensate. Event-driven architectures, on the other hand, encourage loose coupling. But we are still bound by lessor forms of coupling such as schema coupling. And here lies a question that many students and clients have asked me: “How do I version my...