BackwardCompatibility is the tool that helps us, software engineers, practice the style of DistributedDevelopment. In the situation when we build our applications from hundreds and hundreds of external libraries, when we cannot control their schedule, yet we need new upgrades of such downstream libraries. BackwardCompatibility is the mantra that allows us reach smooth Upgradability of such components in our systems.
Without Backward Compatibility
Does software engineering need BackwardCompatibility? It is good for anything else than restricting poor API designers with rules of what they cannot do? Surprising answer to such question is obtained after realizing that incompatibilities cause NP-Complete problems.
The more incompatibility we put into our libraries, the more harder it will be to compose our systems, so various versions of each library can co-exist. Each incompatible library version adds additional variable to the wikipedia::3SAT problem. Things are not that bad when we have just a limited set of incompatible libraries, but if the number grow - then finding working configuration of an application might almost solve quite complex wikipedia::3SAT problems. Something that is know to be really hard.
Symptoms of such problems are more common in libraries that are not yet popular enough. Their developers may not yet be trained in keeping backward compatibility. However the more popular your library becomes the more you will care. As a result often used libraries are developed in compatible way. Finding whether module dependencies can be satisfied in a repository with only compatible versions is obviously trivial.
But imagine glibc developers went mad and published an incompatible version. As that library is used by everyone, there would be quite a lot of NP-complete problems to solve.
Thus the NP-Complete nature of library versioning problem can also be seen as an advertisement for importance of BackwardCompatibility. While we keep it, we live in a simple world, as soon as we start to release Big Bang incompatible revisions, we'll encounter the NP complexity.
Types of Compatibility
Two versions of the same library are source compatible if any program written and successfully compiled against the older version can also be successfully compiled against the new version.
Obviously this means that one cannot remove or rename accessible objects in the API of the library. What has once been discovered needs to continue to shine on and follow the rules of proper API Design until eternity.
However this kind of compatibility is not easy to reach in Java, as also almost any addition can cause problems. Especially due to language constructs like wildcard import.
Two versions of the same library are binary compatible if any program written and successfully compiled into appropriate binary form against older version of the library, can link (its binary form) with the newer version.
This kind of compatibility is important for assemblers and users of final systems. They usually operated on compiled bits, download them from whatever site and then they expect these bits will work on their system. Usually two applications are often compiled against different versions of libraries, and only due to binary compatibility (if present) they can all successfully link on the final system.
Keeping binary compatibility in Java needs a little bit of understanding of the Java virtual machine's lingua franca - the bytecode. The concepts of the bytecode are very similar to concepts of Java language, yet there are differences. As discussed in Chapter 3 it is quite important for a good API designer to understand them. Probably the most important one is that all the Generics features of Java languages are stripped off and erased during compilation and are not present in the final class file (as of JDK5, JDK6).
Two versions of the same library are functionally compatible if any code written, compiled and linked and executed against the older version can successfully be executed against the new version and produces the same result.
This kind of compatibility is quite tricky, as it covers a lot of Runtime Aspects of APIs, yet in the end it is the most important one. In case of BackwardCompatibility we actually do not care that much whether our application compiles and links, but whether it really works, and only applications written against libraries that are functionally compatible can safely upgrade versions of their libraries without fear of becoming broken.
To get better feel for this kind of compatibility, play or read about the API Fest'08 game.
What could be the best classification of BackwardCompatibility?
If some expected behavior is present in previous version but not in subsequent one, then backward compatibility is compromised.
Of course, there are two terms that need further definition.
behavior is present - I'd say that if a program written, compiled and executed against the API produces a result (prints something, shows something on screen, crashes, etc.), then such behavior is present.
expected - I believe API providers should sacrifice themselves as much as possible in favor of API users. The maximum level is 100% compatibility that says it is expected that any program written against the previous version of an API must produce the same result in subsequent versions. Honestly, this kind of compatibility is hard to achieve and suitable mostly for APIFests. That is why NetBeans is using a weaker version of 99% compatibility that allows to change a behavior when it is clear it was not useful for anyone (e.g. no longer throw NullPointerException in newer version, etc.). Rather than changing existing behavior, we are offering alternative behavior. In any case we are trying to minimize the Amoeba effect for our users.
I am used to require the same level of API customer care from others. Does it sounds too drastic and would you rather use more leisure compatibility mode?