Downgrades modern java bytecode to older versions. at either compile or runtime.
This is currently capable of downgrading from Java 22 to Java 8. Java 7 may come in the future.
Currently attempting to downgrade to Java 7 will produce valid class files, but some of the API stubs are broken, and many common ones dont exist.
It is recommended to use the shade task/cli as documented below, as it will only include necessary methods in your jar.
alternatively, you can add the api jar in its entirety to the classpath when running the jar.
The api jar can be found at xyz.wagyourtail.jvmdowngrader:jvmdowngrader-java-api:0.9.0:downgraded-8
there is also a downgraded-11
jar there.
alternatively, to produce other versions you can generate one yourself using the cli:
java -jar JvmDowngrader-all.jar -c 53 debug downgradeApi ./java-api-9.jar
Api jars for older java versions can still be used for newer java, but may not be as efficient.
This downgrades the output of a jar task using another task.
Note that certain things like reflection and dynamic class definition downgrading will not work without runtime
downgrading.
dynamic class definitions being things like MethodHandles$Lookup#defineClass
and classloader shenanigans.
add my maven in settings.gradle
:
JvmDowngrader is now on the gradle plugin portal, so you can skip this step, unless you want to use a snapshot version.
pluginManagement {
repositories {
mavenLocal()
maven {
url = "https://maven.wagyourtail.xyz/releases"
}
maven {
url = "https://maven.wagyourtail.xyz/snapshots"
}
mavenCentral()
gradlePluginPortal()
}
}
in build.gradle
:
// add the plugin
plugins {
id 'xyz.wagyourtail.jvmdowngrader' version '$jvmdgVersion'
}
// optionally you can change some globals, here are their default values:
jvmdg.downgradeTo = JavaVersion.VERSION_1_8
jvmdg.apiJar = [this.getClass().getResourceAsStream("jvmdg/java-api-${version}.jar").writeToFile("build/jvmdg/java-api-${version}.jar")]
jvmdg.quiet = false
jvmdg.debug = false
jvmdg.logLevel = "INFO"
jvmdg.ignoreWarningsIn = [].toSet()
jvmdg.debugSkipStubs = [].toSet()
jvmdg.debugDumpClasses = false
jvmdg.shadePath = {
it.substringBefore(".").substringBeforeLast("-").replace(Regex("[.;\\[/]"), "-")
}
jvmdg.shadeInlining = true
jvmdg.multiReleaseOriginal = false
jvmdg.multiReleaseVersions = [].toSet()
You can see more of what these values mean by checking the docs on the flags classes
This will create a default downgrade task for jar
(or shadowJar
if present) called downgradeJar
that will
downgrade the output to java 8 by default.
as well as a shadeDowngradedApi
to then insert the required classes for not having a runtime dependency on the api
jar.
you can change the downgrade version by doing:
downgradeJar {
downgradeTo = JavaVersion.VERSION_11
}
shadeDowngradedApi {
downgradeTo = JavaVersion.VERSION_11
}
The tasks have all the same flags as the extension, so you can change them separately, their default value is to use the global one from the extension.
If you are merging multiple downgraded jars, please merge from the downgradeJar tasks, and then shade on the resulting mono-jar. otherwise some API stubs may be missing, due to how shade only includes what is used.
Optionally, you can also depend on the shadeDowngradedApi task when running build.
assemble.dependsOn shadeDowngradedApi
task customDowngrade(type: xyz.wagyourtail.jvmdg.gradle.task.DowngradeJar) {
inputFile = tasks.jar.archiveFile
downgradeTo = JavaVersion.VERSION_1_8 // default
classpath = sourceSets.main.compileClasspath // default
archiveClassifier = "downgraded-8"
}
task customShadeDowngradedApi(type: xyz.wagyourtail.jvmdg.gradle.task.ShadeJar) {
inputFile = customDowngrade.archiveFile
archiveClassifier = "downgraded-8-shaded"
}
task downgradeFileCollection(type: xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles) {
inputCollection = files("file1.jar", "file2.jar")
downgradeTo = JavaVersion.VERSION_1_8 // default
classpath = sourceSets.main.runtimeClasspath // default
}
// get the output with
downgradeFileCollection.outputCollection
task shadeFileCollection(type: xyz.wagyourtail.jvmdg.gradle.task.files.ShadeFiles) {
inputCollection = downgradeFileCollection.outputCollection
downgradeTo = JavaVersion.VERSION_1_8 // default
}
// get the output with
shadeFileCollection.outputCollection
Make sure the task is configured before trying to use the outputCollection,
it's computed from the toDowngrade
files.
To depend on more modern jars on a lower java version you can downgrade the configuration.
configurations {
downgrade
implementation.extendsFrom downgrade
}
jvmdg.dg(configurations.downgrade) {
downgradeTo = JavaVersion.VERSION_1_8 // default
}
dependencies {
downgrade "newer.java:version:1.0"
}
This is the expected usage of these functions.
jvmdg.dg
has optional parameter, shade: Boolean
, turn this to false if you plan on shadow'ing this configuration and
running shadeDowngradedApi
, as otherwise there may be duplicate jvmdg-api classes.
ie. jvmdg.dg(configurations.downgrade, false)
you may have to include whatever configuration you're downgrading in ShadowJar yourself. I do not automatically make the configuration shadowed into your output.
It is strongly not recommended due to the following but is still possible. instead, you should just shade dependencies into your project and downgrade the combined output. The dependencies may not be correctly represented in the pom with this method.
There may be issues with metadata breaking because of how early gradle checks the java version,
you can disable this by setting artifact
in the metadataSources
function on repositories
to explicitly disable metadata, if you are lucky, you may be able to include mavenPom
as well, so trasitive dependencies can resolve, otherwise
transitive dependencies will not be included, and must be included manually.
for example:
repositories {
mavenCentral {
metadataSources {
mavenPom() // may still break with this line on some dependencies
artifact()
}
}
}
Downgrades the contents of a zip file or path to an older version. you can specify multiple targets for bulk operations. (and they will include eachother in classpath searches during downgrading)
ex. java -jar JvmDowngrader-all.jar -c 52 downgrade -t input.jar output.jar -cp classpath.jar -cp classpath2.jar
Analyze a downgraded zip file for API usages and shade them into the output. you can specify multiple targets for bulk operations.
ex. java -jar JvmDowngrader-all.jar -c 52 shade -p "shade/prefix" -t input.jar output.jar
The class version can be replaced with a path to the pre-downgraded api jar to save time, using -d
.
This is basically only here so I can take funny screenshots of minecraft running on java 8. I recommend the agent method, as it's most reliable.
Uses the java agent to downgrade at runtime.
ex. java -javaagent:JvmDowngrader-all.jar -jar myapp.jar
Uses the bootstrap main class
ex. java -jar JvmDowngrader-all.jar bootstrap -cp myapp.jar;classpath.jar;classpath2.jar --main mainclass args
This is what the bootstrap downgrader essentially uses internally.
// add jar to default downgrading classloader
ClassDowngrader.getCurrentVersionDowngrader().getClassLoader().addDelegate(new URL[] { new File("jarname.jar").toURI().toURL() });
// call main method
ClassDowngrader.getCurrentVersionDowngrader().getClassLoader().loadClass("mainclass").getMethod("main", String[].class).invoke(null, new Object[] { new String[] { "args" } });
You can also create your own downgrading classloader, for more complicated environments.
DowngradingClassLoader loader = new DowngradingClassLoader(ClassDowngrader.getCurrentVersionDowngrader(), parent);
// adding jars
loader.addDelegate(new URL[]{new File("jarname.jar").toURI().toURL()});
https://github.com/Chocohead/Not-So-New and https://github.com/luontola/retrolambda