My small stint with Java decompiler and solutions to the roadblocks I faced

Recently I came across a problem where we need to do some changes in a small application where we don’t have the source code.

Around 5 years back my team members built a small application for a temporary purpose and we used it for a while and forgot about it. Recently we got a similar kind of requirement again temporarily. We got the jar file which we deployed at that time. We have a small change in requirement but we realized we lost this application code at the time of SVN to Git migration. For a temporary requirement, we don’t see a point of building whole application again so we thought why not decompile the code and change it a bit and deploy it again.

In this process, I faced a couple of roadblocks I thought it’s worth sharing. To share the journey with you all let me create an example project and play with it. I built a small application which converts weights & distances from one measurement to another. You will find it’s source and jar file here. You can find the jar too.

To run this application you need to run one of the following commands

java -cp jar/java-decompile-example-1.0.0.jar com.arvind.converter.Converter
                            OR
mvn exec:java -Dexec.mainClass="com.arvind.converter.Converter"

You will get output like this

Which converter you want to play with (distance, weight)?
distance
Convert from (in, ft, mi, mm, cm, m, km):
m
Convert to (in, ft, mi, mm, cm, m, km): 
km  
Value:
1012 
1012.0 m = 1.0 km

Now let’s assume we don’t have the source code and we want to do a couple of changes.
1) We are unnecessarily doing rounding so we want to remove it.
2) Inch to meter conversion value from 0.0254001 to 0.0254.
3) Printing message mistake. “to” need to be written in the place of ”from” for one of the weight conversion message dialogues.

Let’s start the interesting part now.

First, to decompile the code we need Java Decompiler. Right now I am using ubuntu 18.04 so with the simple command I am able to install my favorite decompiler JD-GUI.

sudo apt-get install jd-gui

or else you can follow the steps given in JD-GUI GitHub repository.

git clone https://github.com/java-decompiler/jd-gui.git
cd jd-gui
./gradlew build

We need to open our jar file through JD-GUI.

Let’s pick the first task and do it. We need to remove Round functionality. Basically, we are rounding in UnitConverter level so let’s take those decompiled files and do the changes.

We need java files to change so we can save those decompiled class files as java files. Open the class file which you want to save.

Then save it.

Now let’s do our first task.

Task 1:
Now I will remove the Math.round so that unncessary rounding will be gone.

Now our java files are ready so let’s compile them and create new class files. At the time of compilation, we need to thought about a couple of things.

  1. Classpath (Dependency jars and etc.)
    If we don’t put it correctly we will get a lot of compilation issues.
  2. Java version on which we are targetting. If we don’t put it correctly then our other class files are in different java version and the current class file on which java file we are working will be in the different version then we get Unsupported major.minor version error. Don’t forget we built this jar using Java 1.6 version.

Compilation command will be like this

javac -source 1.6 -target 1.6 -classpath ../RealTimeData.jar ../RealTimeDataAccess.java

When you run this command most probably you will get below warning & errors.

warning: [options] bootstrap class path not set in conjunction with -source 1.6
UnitConverter.java:28: error: variable meters is already defined in method toMeters(double)
    double meters;
           ^
UnitConverter.java:35: error: variable meters is already defined in method toMeters(double)
      double meters;
             ^
UnitConverter.java:42: error: variable meters is already defined in method toMeters(double)
        double meters;
               ^
UnitConverter.java:49: error: variable meters is already defined in method toMeters(double)
          double meters;
                 ^
UnitConverter.java:56: error: variable meters is already defined in method toMeters(double)
            double meters;
                   ^
UnitConverter.java:63: error: variable meters is already defined in method toMeters(double)
              double meters;
                     ^
UnitConverter.java:80: error: variable converted is already defined in method fromMeters(double)
    double converted;
           ^
UnitConverter.java:87: error: variable converted is already defined in method fromMeters(double)
      double converted;
             ^
UnitConverter.java:94: error: variable converted is already defined in method fromMeters(double)
        double converted;
               ^
UnitConverter.java:101: error: variable converted is already defined in method fromMeters(double)
          double converted;
                 ^
UnitConverter.java:108: error: variable converted is already defined in method fromMeters(double)
            double converted;
                   ^
UnitConverter.java:115: error: variable converted is already defined in method fromMeters(double)
              double converted;
                     ^
12 errors
1 warning

Don’t panic these kinds of errors are quite common. If you see closely the decompiled java file has many times declaration for converted and meters.

private double fromMeters(double meters)
  {
    double converted;
    double converted;
    if (this.toUnit.equals("in"))

Remove unnecessary declaration of converted and meters and remove unnecessary System.out.println statements in convert function if there are any. Do the same in the other UnitConverter.java too.

Now compile these java files again. You will find the class files. Now we need to put our class files in the jar files. This is simple we can replace them in jar file only by using Archive Manager.

Open the jar file using Archive Manager and go the folder where you want to replace your class file and click on add files

Now select the class file.

Now your class file will be replaced.

You can see the modified file.

Now run the jar file using the following command

java -cp java-decompile-example-1.0.0.jar com.arvind.converter.Converter

You will get output like this

Which converter you want to play with (distance, weight)?
distance
Convert from (in, ft, mi, mm, cm, m, km):
m
Convert to (in, ft, mi, mm, cm, m, km): 
km
Value:
1012
1012.0 m = 1.012 km

Congrats!!! Your first task is done. Not bad right.

Now let’s do the task 2.

Task 2:
Inch to meter conversion value from 0.0254001 to 0.0254.

We need to do the changes in UnitConverter you might be wondering we kept our constants in Constants java file but Ashok is saying do the changes in UnitConverter. This is due to one of the beauties of Compilation time optimizations. We are declaring Constants in a single place and using in multiple places so that if we do changes at one place but the compiler doesn’t see any point of getting the value from a different place every time so it just put the value there.

Now change the 0.0254001 to 0.0254 at the following line.

private double toMeters(double val)
  {
    double meters;
    if (this.fromUnit.equals("in"))
    {
      meters = val * 0.0254001D;
    }

Same steps again compile it and replace it in the jar file. Your second task also completed. Congrats again.

Now let’s do the task 3.

Task3:
Printing message mistake. “to” need to be written in the place of ”from” for one of the weight conversion message dialogues.

At the time of weight conversion, you can see below that input question have from instead of to

Which converter you want to play with (distance, weight)?
weight
Convert from (lb, kg, g, mg, oz, t):
g
Convert from (lb, kg, g, mg, oz, t):
oz
Value:
102
102.0 g = 3.597948 oz

Let’s get decompiled version java file for Converter and do the changes and compile it. Now you might get the following error.

warning: [options] bootstrap class path not set in conjunction with -source 1.6
Converter.java:7: error: a type with the same simple name is already defined by the single-type-import of UnitConverter
import com.arvind.converter.weight.UnitConverter;
^
1 error
1 warning

This error coming because we have 2 UnitConverter java files in different packages and using both of them in a single place. Here we need to use them with the full package instead of importing or else one we can import and another we can use with the full package. So in these cases decompiler get confused and writes both import statements.

Here we can remove both import statements and we can use like this

if (isItDistanceConverter) {
      converter = new com.arvind.converter.distance.UnitConverter(fromUnit, toUnit);
} else {
      converter = new com.arvind.converter.weight.UnitConverter(fromUnit, toUnit);
}

Now let’s compile it and replace in the jar. Run the code again. Now you will get proper output

Which converter you want to play with (distance, weight)?
weight
Convert from (lb, kg, g, mg, oz, t):
g
Convert to (lb, kg, g, mg, oz, t):
oz
Value:
1346
1346.0 g = 47.478804 oz

Congrats once again for completing the 3rd task!!!

Peace. Happy Coding.

Related Post

Leave a Reply

Your email address will not be published. Required fields are marked *