API Versioning for Mobile Apps: Handling Old Versions Safely

API Versioning for Mobile Apps: Handling Old Versions Safely — cover image

On the web, you deploy a fix and every visitor gets it within minutes. On mobile, the story is different. After you publish a new version, some users update within a day, some within a month and some never. A person with limited storage or a spotty connection may run the same build for a year. Meanwhile, your backend keeps changing. If an old app calls an endpoint that no longer behaves as expected, it crashes, shows blank screens or silently loses data.

This guide explains how to design API versioning and backward compatibility for mobile apps, so you can keep shipping without breaking people who have not updated. It is written for founders and product teams who run both an app and a backend and want predictable releases.

The Core Problem: You Do Not Control the Client

With server rendered web pages, the server controls what the user sees. With mobile apps, the client contains logic, screens and assumptions about the data format. Those assumptions are frozen at the time the build was compiled. Your server therefore has to honour promises made months ago to apps that you can no longer change.

The challenge grows with the number of platforms. You may have iOS, Android and a web client, each with several live versions. A change that looks trivial in the backend, such as renaming a field from "price" to "amount", can break every client that still expects the old name.

The aim is not to avoid change. It is to make change safe by agreeing on rules for what you may alter freely, what needs a new version and how long you will support the old one.

Breaking and Non-Breaking Changes

The first practical step is a shared definition that everyone on the team can apply.

Usually safe (non-breaking)

Usually breaking

Note the enum case. Many apps crash when they receive a status they do not recognise. Build clients to treat unknown enum values gracefully, with a default display, and document this expectation for every new app version. This simple rule, sometimes called being liberal in what you accept, prevents a large share of mobile backend incidents.

Versioning Strategies Compared

URL path versioning places the version in the route, such as /v1/orders and /v2/orders. It is explicit, easy to log and simple to route at a gateway. The downside is that you may duplicate code across versions if you are not careful.

Header versioning uses a custom header or media type to select the version. It keeps URLs clean but is less visible in logs and harder for developers to test casually.

Additive evolution without new versions avoids major versions by only adding fields and endpoints. This works well for a long time, then breaks down when a genuine redesign is needed.

Client capability negotiation has the app send its version and supported features, and the server adapts the response. It offers flexibility but adds complexity, so use it for specific cases such as feature rollouts.

For most startups, a hybrid works best: additive changes within a major version, and a new URL version only when a breaking change is unavoidable. That keeps the number of live versions small.

A Real World Example: A Delivery App Redesigns Its Order Model

Consider an illustrative scenario. A food delivery startup originally stores an order as a single record with one delivery address. Later it introduces multi stop orders and scheduled deliveries, which requires a list of stops and a new status model. The backend team wants to replace the old structure entirely.

If they simply change the response, every existing app breaks. Instead, they create a v2 order endpoint with the new model while keeping v1 operational. A thin translation layer converts new internal data into the old v1 shape, so the single stop view still works for old apps. The new app version uses v2. The team tracks active users per app version and sets a deprecation date for v1 once adoption of the new version is high. In the meantime, a gentle in app banner nudges stragglers to update.

This approach keeps customers ordering food while the team ships a major change. The cost is some extra translation code, which is far cheaper than an outage during dinner rush.

Step by Step: Building a Safe Versioning Practice

  1. Send the app version with every request. Include app version, build number and platform in a standard header. This enables logging, analytics and targeted behaviour.
  2. Define breaking and non-breaking rules. Write them down in your engineering handbook and apply them in code review.
  3. Add contract tests. Keep sample requests and responses for each supported version, and run them in CI so a backend change that breaks an old contract fails the build.
  4. Make clients tolerant. Ignore unknown fields, handle unknown enum values and avoid crashing on missing optional data.
  5. Create a minimum supported version setting. Store it on the server so you can change it without releasing an app update.
  6. Build a force update and soft update flow. A soft prompt suggests an update. A blocking screen is reserved for security fixes or incompatible changes.
  7. Track version adoption. Monitor the share of active users by app version and the error rates for each.
  8. Announce deprecations. Give users and internal teams a clear timeline, show in app messages before retirement and keep release notes up to date.
  9. Use feature flags for risky changes. Ship code dark and enable it gradually. Our article on feature flags and progressive rollouts covers how to do this safely.
  10. Retire old versions deliberately. Remove code for a version only after traffic has dropped and the deprecation period has passed.

Force Updates: Use Them Carefully

A force update screen is a powerful tool and an easy way to annoy users. Reserve it for cases where continuing would risk data loss, security exposure or a legal issue. When you must use one, make the message clear, explain why and give a direct link to the store listing.

Test the flow thoroughly. A broken force update that traps users in a loop is worse than the problem it solves. Remember that store review times and staged rollouts mean the new version might not yet be available to everyone when you flip the switch, so coordinate the minimum version change with actual store availability.

Where possible, reduce the need for updates altogether. Moving presentation logic to the server lets you change layouts without a new build, as we discuss in our guide to server-driven UI for mobile apps. That approach has tradeoffs, but it reduces the number of old client assumptions you must support.

Key Benefits of Disciplined API Versioning

Common Mistakes

The most frequent error is shipping a backend change on Friday afternoon without checking which app versions use the affected endpoint. Always check version traffic before modifying a contract.

Another is supporting too many versions for too long. Every live version multiplies testing and security work. Set a policy, such as supporting the current major version and the previous one, and communicate it.

A third is neglecting database migrations. Schema changes must remain compatible with every supported API version at the time they run, which is easier when you follow the staged approach in our guide to zero downtime database migrations.

Finally, teams often forget analytics and crash reporting. Segment crashes by app version so you can see whether an incident affects only old builds.

On mobile, every release is a promise to every version that came before it.

Working With a Team That Plans for This

Versioning decisions are cheaper to make at the start of a project than after thousands of installs. If you are planning a new product or stabilising an existing one, our team can help with backend contracts, release strategy and client resilience. See our mobile app development services for how we build apps and APIs that evolve safely.

Conclusion

Mobile releases will always be uneven, because you cannot force every user to update at once. The solution is to design for coexistence: send version information, define breaking changes, test contracts, write tolerant clients and plan deprecations with data.

Start with three small actions this week. Add an app version header to every request, set up a server controlled minimum version, and write your breaking change rules into your code review checklist. These steps cost little and protect your users from the most painful kind of failure, the one that happens after an update they never made.

Frequently Asked Questions

Why is API versioning harder for mobile than web?
Web apps update instantly for every user, but mobile apps live on devices. Some users stay on old versions for months, so your backend must serve many app versions at once.
Should we use URL versioning or header versioning?
URL versioning such as /v2/orders is the easiest to understand, test and route. Header based versioning is cleaner in theory but harder to debug. Most teams succeed with URL versions for major changes and additive changes within a version.
What changes are safe without a new version?
Adding optional fields, new endpoints and new enum values that old clients can ignore is generally safe if clients are written to tolerate unknown data. Removing fields, renaming them or changing types is breaking.
How do we force users to update?
Have the app send its version with each request or at launch, and let the server return a minimum supported version. If the app is below it, show a blocking update screen with a store link. Use this sparingly and with advance warning.
How long should we support old versions?
Base it on usage data. Track the share of active users per app version, announce a deprecation date, and retire a version when its traffic is small and you have warned affected users through in app messages.