What is the main obstacle with compatibility in Java?

Compatibility in Java may be sometimes counterintuitive even for experienced developers.  Lets now have a look what is the source of main obstacles.

Standard Java resolves types (of method signatures) during the compilation. As a consequence, exact type matching is required when Java Linker looks-up elements such as methods.

It brings insufficient flexibility when Java libraries (JAR files) evolve. Even a change that would developers widely expect compatible, is incompatible!

It is just because Java differs in source and binary compatibility. In other words, Java Compiler and Java Linker do different jobs. See example:

example-commons-io.png

Client can be compiled with both old and new versions of the library as the update is source compatible. But what happens when a client is compiled with the old version and someone just update to a new version, i.e. he or she replaces the JAR file. The application will fail!

The client cannot be invoked with the new version once it has been compiled with the old one, as the change is not binary compatible. In particular, Java Linker cannot cast String to Object and it throws an error  instead:

java.lang.NoSuchMethodError: org.apache.commons.io.LineIterator.next()Ljava/lang/String;

How can we deal with this? We can educate developers to know the problem. But it is not effective as developers should concentrate on their job. Better, we can provide tools to help with detecting such obstacles.

Scenario 4 – Hidden usage of API and resources

Malicious operations are hidden or prohibited API is called.

Root cause:

  • It is usually caused by lack of awareness or a bad intention
  • Some prohibited API can exist – e.g. application should not invoke threads, access files, perform malicious operations etc.
  • API and resources used by Java application are not explicitly enumerated, thus it is not easy to discover such issues
  • Source code of 3rd party library is not available, thus such one could not be inspected

⇒    As a result it is useful to have overview about API

⇒    It allows for monitoring and blocking of prohibited API usage

Scenario 3 – Distributions are unnecessarily large

Program distributions require more resources than actually needed.

Root cause:

  • A lot of code or whole libraries are not used by application at all
  • Java does not have explicit provided/required API definitions thus unused code cannot be easily discovered
  • Developers tend to rather create code than clean up old parts
  • It is not easy to predict which library can be deleted since dependencies are unknown

Scenario 1 – Nondeterministic program output

Behaviour of application may seem non deterministic as it fails time-to-time.

Cause:

  • Application contains classes duplicated in various libraries
  • Java uses simple resolution → classpath order
  • Classpath order differs on each environment
  • Change of environment triggers loading of different classes
  • Consequence is failure when another (incompatible) class is loaded

Scenario 1 is often consequence of Scenario 2 (Hidden dependency problems).

Scenario 2 – Hidden dependency problems

 Project contains incompatible library versions or multiple versions of one library.

  • There can be many dependencies when downloading necessary 3rd party libraries and these can be easily overlooked by developers.

Root cause:

  • API of 3rd party libraries evolve to accommodate change requests
  • Not only client uses libraries as libraries use their API mutually.

Difficult to predict as libraries sometimes require other libraries in disjunctive version ranges.