Examples

Some typical useful configurations:
  1. A typical application
  2. A typical applet
  3. A typical midlet
  4. All possible applications in the input jars
  5. All possible applets in the input jars
  6. All possible midlets in the input jars
  7. All possible servlets in the input jars
  8. Processing a library
  9. Processing native methods
  10. Processing serializable classes
  11. Processing bean classes
  12. Processing annotations
  13. Processing RMI code
  14. Producing useful obfuscated stack traces
  15. Processing multiple applications at once
  16. Incremental obfuscation
  17. Finding dead code
  18. Printing out the internal structure of class files
You can find some sample configuration files in the examples directory of the ProGuard distribution.  

A typical application

To shrink, optimize, and obfuscate the ProGuard application itself, one would typically create a configuration file proguard.pro and then type:
java -jar proguard.jar @proguard.pro

The configuration file would contain the following options:

-injars       proguard.jar
-outjars      proguard_out.jar
-libraryjars  <java.home>/lib/rt.jar
-printmapping proguard.map
-overloadaggressively
-defaultpackage ''
-allowaccessmodification

-keep public class proguard.ProGuard {
    public static void main(java.lang.String[]);
}

Note the use of the <java.home> system property; it is replaced automatically.

Also note that the type names are fully specified: proguard.ProGuard and java.lang.String[].

The access modifiers public and static are not really required in this case, since we know a priori that the specified class and method have the proper access flags. It just looks more familiar this way.

We're using the -overloadaggressively, -defaultpackage, and -allowaccessmodification options to shave off some extra bytes, but we could leave them out as well. The -defaultpackage directive moves all classes to the given package, in this case the root package. Only proguard.ProGuard keeps its original name.

We're writing out an obfuscation mapping file with -printmapping, for de-obfuscating any stack traces later on, or for incremental obfuscation of extensions.

In general, you might need a few additional directives for processing native methods, serializable classes, bean classes, or annotations. For processing 'simple' applications like ProGuard, that is not required.  

A typical applet

These options shrink, optimize, and obfuscate the applet mypackage.MyApplet:
-injars      in.jar
-outjars     out.jar
-libraryjars <java.home>/lib/rt.jar

-keep public class mypackage.MyApplet

The typical applet methods will be preserved automatically, since mypackage.MyApplet is an extension of the Applet class in the library rt.jar.

If applicable, you should add directives for processing native methods, serializable classes, bean classes, or annotations.  

A typical midlet

These options shrink, optimize, and obfuscate the J2ME midlet mypackage.MyMIDlet:
-injars      in.jar
-outjars     out.jar
-libraryjars /usr/local/java/wtk2.1/lib/midpapi20.jar
-libraryjars /usr/local/java/wtk2.1/lib/cldcapi11.jar
-overloadaggressively
-defaultpackage ''
-allowaccessmodification

-keep public class mypackage.MyMIDlet

Note how we're now targeting the J2ME run-time environment of midpapi20.jar and cldcapi11.jar, instead of the J2SE run-time environment rt.jar. You can target other J2ME environments by picking the appropriate jars.

The typical midlet methods will be preserved automatically, since mypackage.MyMIDlet is an extension of the MIDlet class in the library midpapi20.jar.

If applicable, you should add directives for processing native methods, serializable classes, bean classes, or annotations.

You must preverify your midlets after having processed them, using J2ME's preverify tool. Because this tool unpacks your processed jars, you should use ProGuard's -dontusemixedcaseclassnames option on platforms with case-insensitive filing systems, such as Windows.

Please note the in-depth article "Obfuscating MIDlet Suites with ProGuard" at developers.sun.com.  

All possible applications in the input jars

These options shrink, optimize, and obfuscate all public applications in in.jar:
-injars      in.jar
-outjars     out.jar
-libraryjars <java.home>/lib/rt.jar
-printseeds

-keepclasseswithmembers public class * {
    public static void main(java.lang.String[]);
}

Note the use of -keepclasseswithmembers. We don't want to preserve all classes, just all classes that have main methods, and those methods.

The -printseeds option prints out which classes exactly will be preserved, so we know for sure we're getting what we want.

If applicable, you should add directives for processing native methods, serializable classes, bean classes, or annotations.  

All possible applets in the input jars

These options shrink, optimize, and obfuscate all public applets in in.jar:
-injars      in.jar
-outjars     out.jar
-libraryjars <java.home>/lib/rt.jar
-printseeds

-keep public class * extends java.applet.Applet

We're simply keeping all classes that extend the Applet class.

Again, the -printseeds option prints out which applets exactly will be preserved.

If applicable, you should add directives for processing native methods, serializable classes, bean classes, or annotations.  

All possible midlets in the input jars

These options shrink, optimize, and obfuscate all public J2ME midlets in in.jar:
-injars      in.jar
-outjars     out.jar
-libraryjars /usr/local/java/wtk2.1/lib/midpapi20.jar
-libraryjars /usr/local/java/wtk2.1/lib/cldcapi11.jar
-overloadaggressively
-defaultpackage ''
-allowaccessmodification
-printseeds

-keep public class * extends javax.microedition.midlet.MIDlet

We're simply keeping all classes that extend the MIDlet class.

And again, the -printseeds option prints out which midlets exactly will be preserved.

If applicable, you should add directives for processing native methods, serializable classes, bean classes, or annotations.

You must preverify your midlets after having processed them, using J2ME's preverify tool. Because this tool unpacks your processed jars, you should use ProGuard's -dontusemixedcaseclassnames option on platforms with case-insensitive filing systems, such as Windows.  

All possible servlets in the input jars

These options shrink, optimize, and obfuscate all public servlets in in.jar:
-injars      in.jar
-outjars     out.jar
-libraryjars <java.home>/lib/rt.jar
-libraryjars /usr/local/java/servlet/servlet.jar
-printseeds

-keep public class * implements javax.servlet.Servlet

Keeping all servlets is very similar to keeping all applets. The servlet API is not part of the standard run-time jar, so we're specifying it as a library. Don't forget to use the right path name.

We're then keeping all classes that implement the Servlet interface. We're using the implements keyword because it looks more familiar in this context, but it is equivalent to extends, as far as ProGuard is concerned.

And again, the -printseeds option prints out which servlets exactly will be preserved.

If applicable, you should add directives for processing native methods, serializable classes, bean classes, or annotations.  

Processing a library

These options shrink, optimize, and obfuscate an entire library, keeping all public and protected classes and class members, native method names, and serialization code:
-injars       in.jar
-outjars      out.jar
-libraryjars  <java.home>/lib/rt.jar
-printmapping out.map

-renamesourcefileattribute SourceFile
-keepattributes InnerClasses,SourceFile,LineNumberTable,Deprecated,
                Signature,*Annotation*,EnclosingMethod

-keep public class * {
    public protected *;
}

-keepclassmembernames class * {
    java.lang.Class class$(java.lang.String);
    java.lang.Class class$(java.lang.String, boolean);
}

-keepclasseswithmembernames class * {
    native <methods>;
}

-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

-keepclassmembers class * extends java.lang.Enum {
    public **[] values();
}

This configuration should preserve everything we'll ever want to access in the library. Only if there are any other non-public classes or methods that are invoked dynamically, they should be specified using additional -keep directives.

The -keepclassmembernames directive for the class$ methods is not strictly necessary. These methods are inserted by the javac compiler and the jikes compiler respectively, to implement the .class construct. ProGuard will automatically detect them and deal with them, even when their names have been obfuscated. However, older versions of ProGuard and other obfuscators may rely on the original method names. It may therefore be helpful to preserve them, in case these other obfuscators are ever used for further obfuscation of the library.

We've added some directives for keeping the "Deprecated" attribute, for useful stack traces, for native methods, serializable classes, and for annotations (possibly with enumerations), which are all discussed in their respective examples.

The "InnerClasses" attribute (or more precisely, its source name part) has to be preserved as well, for any inner classes that can be referenced from outside the library. The javac compiler would be unable to find the inner classes otherwise.

The "Signature" attribute is required to be able to access generic types when compiling in JDK 5.0.  

Processing native methods

If your application, applet, servlet, library, etc., contains native methods, you'll want to preserve their names and their classes' names, so they can still be linked to the native library. The following additional option will ensure that:
-keepclasseswithmembernames class * {
    native <methods>;
}

Note the use of -keepclasseswithmembernames. We don't want to preserve all classes or all native methods; we just want to keep the relevant names from being obfuscated.

ProGuard doesn't have your native code, so it can't automatically preserve the classes or class members that are invoked by the native code. These are entry points, which you'll have to specify explicitly.  

Processing serializable classes

More complex applications, applets, servlets, libraries, etc., may contain classes that are serialized. Depending on the way in which they are used, they may require special attention:

Note that the above directives may preserve more classes and class members than strictly necessary. For instance, a large number of classes may implement the Serialization interface, yet only a small number may actually ever be serialized. Knowing your application and tuning the configuration will often produce more compact results.  

Processing bean classes

If your application, applet, servlet, library, etc., uses extensive introspection on bean classes to find bean editor classes, or getter and setter methods, then configuration may become laborious. There's not much else you can do than making sure the bean class names, or the getter and setter names don't change. For instance:
-keep public class mypackage.MyBean {
    public void setMyProperty(int);
    public int getMyProperty();
}

-keep public class mypackage.MyBeanEditor

If there are too many elements to list explicitly, wildcards in class names and method signatures might be helpful. This example encompasses a wide range of setters and getters:

-keep class mybeans.** {
    void set*(%);
    void set*(**);
    void set*(%[]);
    void set*(**[]);
    void set*(int, %);
    void set*(int, **);

    %    get*();
    **   get*();
    %[]  get*();
    **[] get*();
    %    get*(int);
    **   get*(int);
}

The '%' wildcard matches any primitive type, while the '**' wildcard matches any class name. Array types have to be specified explicitly. This results in a short list of alternative method signatures.  

Processing annotations

As of JDK 5.0, class files can contain annotations. Annotations are represented by attributes that have no direct effect on the execution of the code. However, their values can be retrieved through introspection, allowing developers to adapt the execution behavior accordingly. By default, ProGuard treats annotation attributes as optional, and removes them in the obfuscation step. If they are required, you'll have to specify this explicitly:
-keepattributes *Annotation*

-keepclassmembers class * extends java.lang.Enum {
    public static **[] values();
}

For brevity, we're specifying a wildcarded attribute name, which will match RuntimeVisibleAnnotations, RuntimeInvisibleAnnotations, RuntimeVisibleParameterAnnotations, RuntimeInvisibleParameterAnnotations, and AnnotationDefault. Depending on the purpose of the processed code, you could refine this selection, for instance not keeping the run-time invisible annotations (which are only used at compile-time).

If your annotations contain enumeration classes, you'll have to preserve the values() method in these classes. The run-time environment accesses this method by introspection (Isn't that just grand? Introspection is the self-modifying code of a new generation).

Some code may make further use of introspection to figure out the enclosing methods of anonymous inner classes. In that case, the corresponding attribute will have to be preserved as well:

-keepattributes EnclosingMethod
 

Processing RMI code

Reportedly, the easiest way to handle RMI code is to process the code with ProGuard first and then invoke the rmic tool. If that is not possible, you may want to try something like this:
-keep interface * extends java.rmi.Remote {
    <methods>;
}

-keep class * implements java.rmi.Remote {
    <init>(java.rmi.activation.ActivationID, java.rmi.MarshalledObject);
}

The first directive keeps all your Remote interfaces and their methods. The second one keeps all the implementations, along with their particular RMI constructors, if any.  

Producing useful obfuscated stack traces

These options let obfuscated applications or libraries produce stack traces that can still be deciphered later on:
-printmapping out.map

-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable

We're keeping all source file attributes, but we're replacing their values by the string "SourceFile". We could use any string. This string is already present in all class files, so it doesn't take up any extra space. If you're working with J++, you'll want to keep the "SourceDir" attribute as well.

We're also keeping the line number tables of all methods.

Whenever both of these attributes are present, the Java run-time environment will include line number information when printing out exception stack traces.

The information will only be useful if we can map the obfuscated names back to their original names, so we're saving the mapping to a file out.map. The information can then be used by the ReTrace tool to restore the original stack trace.  

Processing multiple applications at once

You can process several independent applications (or applets, midlets,...) in one go, in order to save time and effort. ProGuard's input and output handling offers various ways to keep the output nicely structured.

The easiest way is to specify your input jars (and/or wars, ears, zips, and directories) and a single output directory. ProGuard will then reconstruct the input in this directory, using the original jar names. For example, showing just the input and output options:

-injars  application1.jar
-injars  application2.jar
-injars  application3.jar
-injars  application.war
-outjars processed_applications

After processing, the directory processed_applications will contain the processed application jars and war, with their original names.

If you want explicit control over the output names, or if you want to combine input jars (and/or wars, ears, zips, or directories) into output jars (and/or wars, ears, zips, or directories), you can group the -injars and -outjars options. For example:

-injars  application1.jar
-injars  application2.jar
-injars  application3.jar
-outjars application_out1.war

-injars  application.war
-outjars application_out2.war

This configuration creates two wars: one containing all processed application jars, and one containing the processed contents of the input war.

Note that ProGuard will try to package output archives in a sensible way, reconstructing the input entries as much as required. If you want even greater control, you can add filters to the input and the output, filtering out zips, ears, wars, jars, and/or ordinary files.  

Incremental obfuscation

After having processed an application, e.g. ProGuard itself, you can still incrementally add other pieces of code that depend on it, e.g. the ProGuard GUI:
-injars       proguardgui.jar
-outjars      proguardgui_out.jar
-libraryjars  <java.home>/lib/rt.jar
-libraryjars  proguard.jar
-applymapping proguard.map

-keep public class proguard.gui.ProGuardGUI {
    public static void main(java.lang.String[]);
}

We're reading both unprocessed jars: the new one as the input jar, and the old one as a library jar. The -applymapping option then makes sure the ProGuard part of the code gets the previously produced obfuscation mapping. The final application will consist of the obfuscated ProGuard jar and the additional obfuscated GUI jar.  

Finding dead code

These options list unused fields and methods in the application mypackage.MyApplication:
-injars      in.jar
-libraryjars <java.home>/lib/rt.jar
-dontoptimize
-dontobfuscate
-printusage

-keep public class mypackage.MyApplication {
    public static void main(java.lang.String[]);
}

We're not specifying an output jar, just printing out some results.

We're saving a little bit of time by skipping the optimization and obfuscation steps.  

Printing out the internal structure of class files

These options print out the internal structure of all class files in the input jar:
-injars in.jar
-dontshrink
-dontoptimize
-dontobfuscate
-dump

Note how we don't need to specify the Java run-time jar, because we're not processing the input jar at all.


Copyright © 2002-2005 Eric Lafortune.