Why is ProGuard generating code that requires `--enable-preview`

I updated our codebase from JDK 15 to JDK 16. On JDK 15 all was working fine with or without ProGuard. Running the code in development on JDK 16 works totally fine. However, going through ProGuard 7.1.1, there is an exception when run:

cannot link Java class com.company.SomeClass Preview features are not enabled for com/company/SomeClass (class file version 60.65535). Try running with '--enable-preview'

I don’t see a reason for this. I’m not using any preview features. Why is ProGuard generating code that requires that flag?

Another thing to note is a bullet in the 7.1.0 release notes. Here it says that:

Add support for records (previewed in Java 15/16, targeted for Java 17).

Which is not true. Records are final in JDK 16, they are not a preview feature there. Not sure if it is relevant to the issue above (although I’m not using Records in my application).

1 Like

Dear @boris ,

Thank you for asking this question here on the community.

This exception might show up if you use some dependencies who are on their turn using preview features. Could you maybe share the full log and a sample reproducing the issue?

With regards to the note you made, thanks for catching that one! It looks like this release note got mixed up with the one about “sealed classes”.

Kind regards,

Ewout

Thanks for the answer!

I’ve been trying to reproduce for the past hour. It’s obviously more difficult than just adding all my dependencies as that works fine. So there must be something else that triggers ProGuard to generate such class files. Is there a way to find which dependency might “turn on” preview features? Or a way to find out why ProGuard decides to generate class files that require the preview-features flag?

Regards,
Boris

OK, I got it! Make sure you’re using Gradle 7 and JDK 16.

build.gradle:

buildscript {
	repositories {
		mavenCentral()
	}

	dependencies {
		classpath 'com.guardsquare:proguard-gradle:7.1.1'
	}
}

plugins {
	id 'war'
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.apache.tomcat:tomcat-catalina:9.0.50'
}

sourceCompatibility = JavaVersion.VERSION_16

def commonProGuardOptions = { sourceSets, outputDir, build -> { ->
	def sourceSetsRuntimeClasspath = sourceSets
		.collectMany { sourceSet ->
			(sourceSet.compileClasspath + sourceSet.runtimeClasspath)
				.filter { !(it.toString() =~ /[\\\/]build[\\\/](classes|resources|libs)[\\\/]/) }
				.collect { it }
		}
		.unique()

	sourceSetsRuntimeClasspath.forEach { libraryjars it }
	fileTree("${System.getProperty('java.home')}/jmods").visit { details ->
		libraryjars details.file.path
	}

	sourceSets.flatten().toSet().forEach { injars it.output }
	outjars "${outputDir}/classes"

	target sourceCompatibility.toString()
	dontshrink

	keepattributes '*Annotation*'

	keepnames 'public class Foo { public *; }'
} }

def productionSourceSets = [[project: project, sourceSet: sourceSets.main]]

tasks.register('proguardWar', proguard.gradle.ProGuardTask) {
	dependsOn productionSourceSets.collect { it.project[it.sourceSet.classesTaskName] }

	def outputDirectory = "${buildDir}/obfuscated-war"

	doFirst { delete outputDirectory }
	doFirst commonProGuardOptions(productionSourceSets.collect { it.sourceSet }, outputDirectory, 'war')
}

war {
	dependsOn 'proguardWar'

	classpath = productionSourceSets
		.collectMany { it.sourceSet.runtimeClasspath.toList() }
		.unique()
		.minus(productionSourceSets.collectMany { it.sourceSet.output.toList() })

	classpath "${buildDir}/obfuscated-war/classes"

	duplicatesStrategy = DuplicatesStrategy.FAIL
	archiveFileName = 'company.war'
}

src/main/java/com/company/servlets/UIServlet.java:

package com.company.servlets;

import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;

import org.apache.catalina.servlets.DefaultServlet;

@WebServlet(
        name = "company-default",
        urlPatterns = "/foobar",
        loadOnStartup = 2,
        initParams = @WebInitParam(name = "precompressed", value = "true"))
public class UIServlet extends DefaultServlet {
}

Run gradle war on this and put the resulting build/libs/company.war file in some Apache Tomcat installation. Run Tomcat and do a curl -vvv localhost:8080/company/foobar. That will lead to:

03-Aug-2021 14:44:14.499 INFO [main] org.apache.catalina.core.ApplicationContext.log Marking servlet [company-default] as unavailable
03-Aug-2021 14:44:14.500 SEVERE [main] org.apache.catalina.core.StandardContext.loadOnStartup Servlet [company-default] in web application [/company] threw load() exception
        java.lang.UnsupportedClassVersionError: Preview features are not enabled for a/a/a/a (class file version 60.65535). Try running with '--enable-preview' (unable to load class [a.a.a.a])
                at org.apache.catalina.loader.WebappClassLoaderBase.findClassInternal(WebappClassLoaderBase.java:2424)
                at org.apache.catalina.loader.WebappClassLoaderBase.findClass(WebappClassLoaderBase.java:865)
                at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1334)
                at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1188)
                at org.apache.catalina.core.DefaultInstanceManager.loadClass(DefaultInstanceManager.java:540)
                at org.apache.catalina.core.DefaultInstanceManager.loadClassMaybePrivileged(DefaultInstanceManager.java:521)
                at org.apache.catalina.core.DefaultInstanceManager.newInstance(DefaultInstanceManager.java:150)
                at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1042)
                at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:983)
                at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:4871)
                at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5180)
                at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
                at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:717)
                at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:690)
                at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:705)
                at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:978)
                at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1849)
                at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
                at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
                at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
                at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:123)
                at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:773)
                at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:427)
                at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1576)
                at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:309)
                at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:123)
                at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:423)
                at org.apache.catalina.util.LifecycleBase.setState(LifecycleBase.java:366)
                at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:936)
                at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:841)
                at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
                at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1384)
                at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1374)
                at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
                at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
                at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
                at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:909)
                at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:262)
                at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
                at org.apache.catalina.core.StandardService.startInternal(StandardService.java:421)
                at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
                at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:932)
                at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
                at org.apache.catalina.startup.Catalina.start(Catalina.java:633)
                at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78)
                at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
                at java.base/java.lang.reflect.Method.invoke(Method.java:567)
                at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:344)
                at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:475)

I believe this is a bug in ProGuard. Should I open an issue in the repo?

P.S. Forgot to add that it’s because of the keepattributes '*Annotation*' line. If I remove that, the error is gone. Any ideas why?

Dear @boris
Thanks a lot for investing some time into this issue.

I tried to make a sample with the information provided but I am seeing the following error when I run the gradle war task:

Caused by: java.lang.IllegalAccessError: class org.gradle.internal.compiler.java.ClassNameCollector (in unnamed module @0x3531d7a1) cannot access class com.sun.tools.javac.code.Symbol$TypeSymbol (in module jdk.compiler) because module jdk.compiler does not export com.sun.tools.javac.code to unnamed module @0x3531d7a1

Google is not really giving me any useful info. I already tried to change my gradle version (JDK is set to 16) in my gradle properties, currently using 7.1.1:

distributionUrl=https://services.gradle.org/distributions/gradle-7.1.1-bin.zip

Would it be possible to make a sample project from your side and send it over to us?

Kind regards,

Ewout

@ewoutd - I made a project here:

https://github.com/boris-petrov/proguard-jdk16-bug

I’ve added instructions in the README. Just clone the repo, copy-paste the instructions in the terminal and you should see the exception.

I’m not sure why you’re getting this error… this is what gradle --version gives me:

./gradlew --version

------------------------------------------------------------
Gradle 7.1.1
------------------------------------------------------------

Build time:   2021-07-02 12:16:43 UTC
Revision:     774525a055494e0ece39f522ac7ad17498ce032c

Kotlin:       1.4.31
Groovy:       3.0.7
Ant:          Apache Ant(TM) version 1.10.9 compiled on September 27 2020
JVM:          16.0.2 (Oracle Corporation 16.0.2+7)
OS:           Linux 5.13.8-arch1-1 amd64

Dear @boris,

Thanks a lot for sharing your sample with us. We were able to identify the issue. We will most likely include the fix in version 7.2.0-beta2 which we will release soon.

Kind regards and thanks again,

Ewout

2 Likes