Decompiling Java Source Code

Image of a cog

In my previous post about the compiler -g debug flag, I mentioned the concept of decompiling Java code. Java decompilers are very useful if you need to retrieve lost source code, or you want to see what code the compiler has automatically generated for you (e.g. for auto-boxing, generics, enums, enhanced for-loops and the like).

Why Decompile Code?

It may happen that we lose some Java source code, due to someone either accidentally or deliberately deleting code, a backup not working or restoring correctly, a version control system failing for some reason, or any of a number of reasons. We can retrieve most or all of our source code if we still have access to the deployed Java class files. We can disassemble or decompile the source code from these class files because the compiled Java class files contain high-level bytecode and a lot of symbolic information.

Unless the class files have been obfuscated before being deployed, we have a very high chance of success. The concept of obfuscation and using an obfuscator will be a topic of a later Java tip.

Often we would like to see what code the compiler auto-generates for us behind the scenes, particularly for the Java 5 enhancements like auto-boxing, generics, enums, and enhanced for-loops.

Finding a Java Decompiler

A good website for play around with six free to use and (mostly) open-source decompilers is http://www.javadecompilers.com/. It’s a very easy website to use: select a decompiler, choose a compiled class file to upload and decompile (occasionally waiting a little while in the queue), and study the output.

I’m going to use two of the six online decompilers. These represent different generations of decompilers.

  • Procyon is a modern decompiler generating Java 8 code.
  • Jad is an older but extremely good decompiler. It’s written in C++ and is very fast. Unfortunately it is now somewhat outdated, unsupported and does not decompile Java 8 code if it contains lambdas.

I would recommend that you try all six decompilers, decide on the most appropriate one for your use, and download and install it locally on your computer.

Enum Example

Here’s some code for a very simple enum:

public enum Cutlery {
    KNIFE,
    FORK,
    SPOON;
}

Here is the output from Procyon:

// Decompiled by Procyon v0.5.36

public enum Cutlery
{
    KNIFE, 
    FORK, 
    SPOON;
}

Here is the output from Jad:

// Decompiled by Jad v1.5.8e. 

public final class Cutlery extends Enum
{
    public static Cutlery[] values()
    {
        return (Cutlery[])$VALUES.clone();
   }

    public static Cutlery valueOf(String s)
    {
        return (Cutlery)Enum.valueOf(Cutlery, s);
    }

    private Cutlery(String s, int i)
    {
        super(s, i);
    }

    public static final Cutlery KNIFE;
    public static final Cutlery FORK;
    public static final Cutlery SPOON;
    private static final Cutlery $VALUES[];

    static 
    {
        KNIFE = new Cutlery("KNIFE", 0);
        FORK = new Cutlery("FORK", 1);
        SPOON = new Cutlery("SPOON", 2);
        $VALUES = (new Cutlery[] {
            KNIFE, FORK, SPOON
        });
    }
}

A big difference! Both decompilers are working with exactly the same class file. The difference is their interpretation of the bytecodes and subsequent generation of code.

Jad decompiles the exact bytecode into the corresponding Java source code (remember that Jad doesn’t know anything about the Java 5 language), while Procyon recognises that this specific pattern of bytecodes represents the code for an enum, and consequently generates a simple enum class.

To verify the output from Jad, you can run javap -verbose Cutlery from the command line, and see that the code generated is correct.

Enhanced For Loop Example

Here’s some code for a very simple enhanced for (for-each) loop:

public class ForEachLoop {

    public static void main(String[] args) {  

        int array[] = {12, 34, 56, 78, 90};
        
        // normal for loop
        for (int i = 0; i < array.length; ++i) {
            System.out.println(array[i]);
        }   

        // enhanced for loop
        for (int x : array) {
            System.out.println(x);
        }
    } 
}

Remember to compile with the -g debug flag to see the exact names of the local variables and parameters.

Here is the output from Procyon:

// Decompiled by Procyon v0.5.36

public class ForEachLoop
{
    public static void main(final String[] args) {
        final int[] array = { 12, 34, 56, 78, 90 };
        for (int i = 0; i < array.length; ++i) {
            System.out.println(array[i]);
        }
        for (final int x : array) {
            System.out.println(x);
        }
    }
}

Here is the output from Jad:

// Decompiled by Jad v1.5.8e. 

import java.io.PrintStream;

public class ForEachLoop
{
    public ForEachLoop()
    {
    }

    public static void main(String args[])
    {
        int array[] = {
            12, 34, 56, 78, 90
        };
        for(int i = 0; i < array.length; i++)
            System.out.println(array[i]);

        int ai[] = array;
        int j = ai.length;
        for(int k = 0; k < j; k++)
        {
            int x = ai[k];
            System.out.println(x);
        }
    }
}

Notice the difference between the decompilers again. Procyon recognises the pattern of bytecodes that represent an enhanced for-each loop, while Jad generates Java code from the exact bytecodes. We can see that the compiler auto-generates the code for a standard for loop with a counter variable and two additional variables to reference the array and hold the length of the array.

Conclusion

Using a decompiler on existing code will allow you to see what the compiler actually generates, and in so doing, will make you more aware when coding.

I recommend that you investigate the results of other common coding practices, such as using auto-boxing and generics, and doing String concatenation with the + sign.

Sign up to get our weekly Java tips.

Leave a Comment

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