Our goal in library development is to resist breaking changes, because breaks interrupt our users and restrict their ease of upgrading to newer releases. Unfortunately, there are times when APIs need to evolve to take into account changes in requirements, understanding, or user demands. There are various ways in which an API may evolve, such as:
- Adding or removing an additional argument into a method.
- Changing the type of an argument into a method.
- Changing the return type of a method.
- Adding additional methods or abstract methods to an interface / abstract class.
In many of these cases, during the evolution of our API, we may be in a position where replaced API is no longer relevant, correct, or the best it could be. We could of course take the easy path and just replace the old API and introduce a break, but this isn't overly user friendly.
Fortunately, we've already talked about semantic versioning elsewhere, so we have in our toolset the ability to do breaking changes when required. However, before we jump to breaking changes and major semantic version increments, we should ensure that we've developed, communicate, and continue over time to adhere to a deprecation policy that allows our users the opportunity to respond to breaking changes before they take effect.
What exactly is deprecation?
For a very long time, Java releases applied the appropriate deprecation annotations, but the API lingered through many releases to ensure backwards compatibility. More recently, the final act of following through on deprecation by removing deprecated functionality has become increasingly accepted. The JDK in more recent releases has taken to cleaning out some deprecated functionality, and improving the @Deprecated
annotation to enable identification of when an API was deprecated, and if it is planned for removal.
It could be said that deprecation is baked into the Java ethos. We should strive to see deprecation and removal as a last resort, but also as a useful tool in our toolset to ensure that we deliver developers the best developer experience.
Whenever we decide to deprecate an API, we should use the JavaDoc @deprecated
tag to document the deprecation, as well as the @Deprecated
annotation.
How do I document a deprecation policy?
A deprecation policy serves two audiences - the users of our library, and ourselves - to ensure we keep ourselves honest and that we don't move too quickly. A good deprecation policy clearly articulates your adherence to semantic versioning, but it goes on to speak about what comes before the breaks. In particular, a deprecation policy is your promise for how long an API will be deprecated before a breaking release removes it. For projects that have a regular release cadence, this is normally measured in terms of number of releases between deprecation and removal. Ideally we would keep this window as wide as possible, and so we shouldn't simply remove a deprecated method at the first chance after the deprecation window has lapsed. In fact, it is preferable to have one big breaking change than a thousand paper cut releases with small amounts of breaking.
A common place to document your deprecation policy is the wiki or readme file of your projects repository. This is appropriate as these places are frequented by developers who will be developing with your library, and is therefore likely to be seen and read.