Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

java: check compatibily between jar versions

Assume we have a set of projects "p1.jar", "p2.jar", etc, all them using library "foo-1.0.0.jar".

Assume a new version "foo-1.0.1.jar" is created. As an additional check, all projects p1, p2, ... are recompiled using new foo-1.0.1.jar. No compilation errors appears.

Now, in the production environment, foo-1.0.0.jar is replaced by foo-1.0.1.jar. No update of p1.jar, p2.jar, ... is done. Unfortunately, everything crashes.

It is possible to do some other checks to be 100% sure new library can replace old one?

Obviously, we are not talking about check of code errors introduced in new library or changes in the functionality (by example, if in new version function "sum" does a subtraction instead of an addition). But at least check changes in arguments, results, available classes, etc.

One example is, foo-1.0.0.jar contains method:

int m1() {
  // some code
  return 0; // always return 0, and ignored by the users
}

that in foo-1.0.1 it is changed to:

void m1() {
  // some code
}

Unfortunately, the result of the method is part of the name mangling in Java (not in C) and p1.jar will fail with "NoSuchMethod" exception when looking for "int m1()".

like image 387
pasaba por aqui Avatar asked Apr 28 '26 13:04

pasaba por aqui


2 Answers

You might use japicmp to compare the new and old library archive for API changes.

But there is still the risk that a dependency of the library you check would not be detected.

Find below an example to demonstrate changes which might be undetected.

ApiClass1.java

class ApiClass {
    public static final int FOO = 23;
    public static void version() {
        System.out.println("version 1 FOO: " + FOO);
    }
}

ApiClass2.java

class ApiClass {
    public static final int FOO = 42;
    public static void version() {
        System.out.println("version 2 FOO: " + FOO);
    }
}

ApiDemo.java

class ApiDemo {
    public static void main(String...args) {
        ApiClass.version();
        System.out.println("ApiClass.FOO: " + ApiClass.FOO);
    }
}
  • compile and build library for API version 1

    javac ApiClass1.java
    jar cf api_v1.jar ApiClass.class
    
  • compile and build library for API version 2

    javac ApiClass2.java
    jar cf api_v2.jar ApiClass.class
    
  • compile, build and run your code with API v1

    javac -cp api_v1.jar ApiDemo.java
    java -cp api_v1.jar:. ApiDemo
    

    output

    version 1 FOO: 23
    ApiClass.FOO: 23
    
  • run your code with API v2, without recompilation

    java -cp api_v2.jar:. ApiDemo
    
    version 2 FOO: 42
    ApiClass.FOO: 23
    

The only reliable way is to compile your code against the new version, run all your unit-/integration test and check if they pass.

like image 164
SubOptimal Avatar answered May 01 '26 04:05

SubOptimal


  1. Unpack both jars, old and new, with jar x
  2. Get class contents with javap
  3. Compare contents from two jars

For example, if you have foo-1.0.0.jar and foo-1.0.1.jar in current directory, you may run this bash script:

# unpack old jar
mkdir foo-1.0.0
cd foo-1.0.0
jar xf ../foo-1.0.0.jar
# unpack new jar
mkdir ../foo-1.0.1
cd ../foo-1.0.1
jar xf ../foo-1.0.1.jar
# get unpacked classes contents
cd ..
for file in `find foo-1.0.0 foo-1.0.1 -name '*.class'`; do javap $file > $file.txt; done
# remove redundant .class files
find foo-1.0.0 foo-1.0.1 -name '*.class' | xargs rm
# compare jar contents
diff -r foo-1.0.0 foo-1.0.1

Sure, that's not fully automated method, you still should to check diff output manually. Still, that's better, than nothing.

like image 20
Evgeny Veretennikov Avatar answered May 01 '26 02:05

Evgeny Veretennikov