Help wanted on shrinking a jar (Kotlin 1.4 + JavaFx 15 + Java 11)

I’ve spent so much time trying to shrink my jar file with Proguard and had no success so far.
Asked this question on GitHub but project admins directed me here.

The original jar file is attached. It is for this open-source application and works without any problem (at least on my machine).
I just want to reduce its size, not obfuscate it.

I used the following configuration.

Any help appreciated.

-injars 'theApp.jar'
-outjars 'out.jar'

-libraryjars 'path\to\jdk-11\jmods'(!**.jar;!module-info.class)

-dontskipnonpubliclibraryclassmembers
-forceprocessing
-dontoptimize
-dontobfuscate
-dontusemixedcaseclassnames
-keepattributes Exceptions,SourceFile,LineNumberTable,*Annotation*,Signature,EnclosingMethod,InnerClasses
-verbose

-keepkotlinmetadata
-dontnote kotlin.internal.PlatformImplementationsKt,kotlin.reflect.jvm.internal.**
-keep class kotlin.Metadata {
    <fields>;
    <methods>;
}

-keepclasseswithmembernames,includedescriptorclasses class * {
    native <methods>;
}

# Keep names of fields marked with @FXML attribute
-keepclassmembers class * {
    @javafx.fxml.FXML
    <fields>;
    @javafx.fxml.FXML
    <methods>;
}

-keep class com.sun.javafx.tk.quantum.QuantumToolkit

-keep class ir.mahozad.donim**{
    <fields>;
    <methods>;
}

# Keep - Applications. Keep all application classes, along with their 'main' methods.
-keepclasseswithmembers public class com.javafx.main.*,ir.mahozad.donim.* {
    public *; public static *;
}

# Also keep - Enumerations. Keep the special static methods that are required in
# enumeration classes.
-keepclassmembers enum  * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# Also keep - Database drivers. Keep all implementations of java.sql.Driver.
-keep class * extends java.sql.Driver

# Also keep - Swing UI L&F. Keep all extensions of javax.swing.plaf.ComponentUI,
# along with the special 'createUI' method.
-keep class * extends javax.swing.plaf.ComponentUI {
    public static javax.swing.plaf.ComponentUI createUI(javax.swing.JComponent);
}

# Keep - Native method names. Keep all native class/method names.
-keepclasseswithmembers,includedescriptorclasses,allowshrinking class * {
    native <methods>;
}

Hi Mahozad,

Could you describe your exact issue to me, is the project crashing at runtime? Are you getting an error while ProGuard is processing the jar?

Please send us the full stack trace and log, it will be crucial to help you tackle this issue.

I tried processing your jar using the latest version of ProGuard with the configuration you included here, ProGuard successfully generated a smaller jar as an output. However, I did not test the program on my machine.

Kind regards,

Jonas

1 Like

Hi,

As you said the output jar is produced successfully but when I try to run it I get a weird java exception saying something about graphics:

Graphics Device initialization failed for :  d3d, sw
Error initializing QuantumRenderer: no suitable pipeline found
java.lang.RuntimeException: java.lang.RuntimeException: Error initializing QuantumRenderer: no suitable pipeline found
        at com.sun.javafx.tk.quantum.QuantumRenderer.getInstance(QuantumRenderer.java:280)
        at com.sun.javafx.tk.quantum.QuantumToolkit.init(QuantumToolkit.java:244)
        at com.sun.javafx.tk.Toolkit.getToolkit(Toolkit.java:261)
        at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:267)
        at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:158)
        at com.sun.javafx.application.LauncherImpl.startToolkit(LauncherImpl.java:658)
        at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:678)
        at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
        at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.RuntimeException: Error initializing QuantumRenderer: no suitable pipeline found
        at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.init(QuantumRenderer.java:94)
        at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:124)
        ... 1 more
Exception in thread "main" java.lang.RuntimeException: No toolkit found
        at com.sun.javafx.tk.Toolkit.getToolkit(Toolkit.java:273)
        at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:267)
        at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:158)
        at com.sun.javafx.application.LauncherImpl.startToolkit(LauncherImpl.java:658)
        at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:678)
        at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
        at java.base/java.lang.Thread.run(Thread.java:834)

The original attached jar can be run on Windows by java 11 or newer without any problem.

I think Proguard must have removed some required classes or resources in the jar. Also, During the process, it logs some warnings about reflection access and a bunch of notes saying maybe this is a library method... and maybe this is a program method...:

Note: com.sun.javafx.application.PlatformImpl: can't find dynamically referenced class com.sun.javafx.embed.swing.SwingFXUtilsImpl
Note: com.sun.javafx.application.LauncherImpl accesses a constructor '<init>()' dynamically

...
maybe this is a library method...
...

Note: there were 341 unkept descriptor classes in kept class members.
      You should consider explicitly keeping the mentioned classes
      (using '-keep').
      (https://www.guardsquare.com/proguard/manual/troubleshooting#descriptorclass)
Note: there were 1 unresolved dynamic references to classes or interfaces.
      You should check if you need to specify additional program jars.
      (https://www.guardsquare.com/proguard/manual/troubleshooting#dynamicalclass)
Note: there were 43 accesses to class members by means of reflection.
      You should consider explicitly keeping the mentioned class members
      (using '-keep' or '-keepclassmembers').
      (https://www.guardsquare.com/proguard/manual/troubleshooting#dynamicalclassmember)
Ignoring unused library classes...
  Original number of library classes: 30404
  Final number of library classes:    1011
Marking classes and class members to be kept...
Inlining subroutines...
Shrinking...
Removing unused program classes and class elements...
  Original number of program classes:            6064
  Final number of program classes:               2554
Preverifying...
Writing output...

I cannot or do not know how to detect these reflection accesses if that’s the problem.

Hello Mahozad!

It looks like ProGuard may be shrinking away some classes that should be kept. In this case, this seems to happen because those classes are only accessed via reflection and the strings used to carry out the reflection are generated on the fly.

You can find an example of this in the code that is mentioned in the exception you got:

java.lang.RuntimeException: java.lang.RuntimeException: Error initializing QuantumRenderer: no suitable pipeline found
        at com.sun.javafx.tk.quantum.QuantumRenderer.getInstance

Looking at the relevant javafx source code, we see the following:

public void init() {
    try {
        if (GraphicsPipeline.createPipeline() == null) {
            String MSG = "Error initializing QuantumRenderer: no suitable pipeline found";
    //...

The createPipeline method shows what’s being accessed via reflection:

//...
            String className = "com.sun.prism."+prefix+"."+prefix.toUpperCase()+"Pipeline";
            try {
                if (PrismSettings.verbose) {
                    System.out.println("Prism pipeline name = " + className);
                }
                Class klass = Class.forName(className);
                if (PrismSettings.verbose) {
                    System.out.println("(X) Got class = " + klass);
                }
                Method m = klass.getMethod("getInstance", (Class[])null);
// ...

So adding a rule like follows should prevent the pipeline classes from being shrunk away:

-keep class com.sun.prism.**Pipeline

Additionally, that same method shows that it will print more information about the classes it does or does not find, when you set the system property prism.verbose to true. This may help in resolving additional issues like this as well. To do this, you can just run the jar with:

java -Dprism.verbose=true -jar out.jar

Using the verbose setting, I found at least one more keep rule that is necessary, as otherwise the Glass UI library cannot be initialized:

-keep class com.sun.glass.ui.win.WinPlatformFactory

I ran into a different error afterwards, but I was unsure whether that was due to my Java installation (I normally don’t use Windows) or due to the Proguard processing. Could you already give it a try with the extra keep rules mentioned above?

Best Regards,
Jago

2 Likes

Thank you so much for your reply.

Unfortunately the resulting jar does not work and Java generates the an error log saying something about EXCEPTION_ACCESS_VIOLATION (0xc0000005) .
I searched for EXCEPTION_ACCESS_VIOLATION (0xc0000005) and tried some of the solutions but had no success.

Did you find any solution to this? I’m struggling with the same problem.

Hi @JohannesOehm,

I’m curious about which problem you are referencing? If you could provide your configuration or sample, that would be appreciated. At first glance it may be a Java or Windows problem? Have you updated to the latest Java version?

Thanks for your time,
Jesse

I’m having the same problem with the EXCEPTION_ACCESS_VIOLATION, but I’m far to unexperienced with native code debugging to pinpoint it further down.

Adding the following rules seems to help:
keep(“class com.sun.glass.ui.** { ; }")
keep("class com.sun.javafx.
* { ; }")
keep("class javafx.fxml.
* { ; }")
keep("class javafx.scene.
* { ; }")
keep("class javafx.geometry.
* { ; }")
keep("class javafx.css.
* { ; }")
keep("class com.sun.prism.shader.
* { ; }")
keep("class com.sun.prism.es2.
* { ; }")
keep("class com.sun.scenario.effect.impl.prism.
* { ; }")
keep("class com.sun.scenario.effect.impl.es2.
* { *; }”)

but it basically just keeps all of the classes of JavaFX inside the .jar, which defeats the purpose of using proguard.

You can see the original code and the code after the fix here: Fixed ProGuard Config (5186bcd4) · Commits · Published / Medic / MRE-Report · GitLab

Hi @JohannesOehm!

It looks like one way to start is to narrow down the rules you’re currently using. Adding -addconfigurationdebugging may work as well.

I hope this helps, and thanks for joining our forums!
Jesse

Hello Jesse,
thank your for your help. I tried to fix my rules and noticed, that a lot of classes are still missing in my minified jar, probably because of reflection. I believe going through all of them and adding them manually might fix my problem. Luckily I found a way to increase my .jar size limit, so I don’t need to use Proguard anymore.

Best Regards
Johannes

1 Like