This is organized as a list of possible messages or signs there is a problem, followed by any ways that help resolve those problems.
This guide is a little new, and will be added to as new solutions are found for problems.
Seriously. Do this first. Many parts of a Gradle project will fail now if you are using too old of a JDK.
You can check this version in IntelliJ IDEA under File -> Project Structure. If you don't have any
JDKs that are version 17 or newer, go into SDKs in the sidebar, and click the +
at the very top of the window. You can
then download a JDK, such as Version: 17
, Vendor: Eclipse Temurin
, which is a solid choice. You can also download a
GraalVM JDK if you want in this way, which enables using LWJGL3's native-image configuration much later on. You can have
many JDKs installed if you want, of various versions.
This would include docs telling you to run a Gradle task like desktop:run
or desktop:dist
.
The solution is almost always to just change desktop
to lwjgl3
, since there is an lwjgl3
module in most Liftoff
projects where gdx-setup would call it desktop
. The reason for this is that Liftoff can generate modules using either
lwjgl2
, which is what gdx-setup used to call desktop
, and lwjgl3
, which is what it now calls desktop
. Liftoff
can even generate them both in one parent project, which can be handy for using gdx-tools
in the lwjgl2
project.
Of the two, you almost always should be using LWJGL3 in new code; LWJGL2 hasn't been updated in 9 years, and won't see
future updates. LWJGL3 also supports more target hardware, from ARM Linux machines like a Raspberry Pi, to M1 and newer
Macs using Apple Silicon processors. You might need LWJGL2 to port older projects in specific cases; doing that prevents
customers who have the latest Mac hardware from running your game natively. Those customers might still be able to use
virtualization software to run Windows games on their Mac, though. LWJGL3 should work out of the box on Mac, thanks in
part to the StartupHelper
class distributed in new LWJGL3 projects that Liftoff creates.
Specifically, the error involves void org.apache.commons.compress.archivers.zip.ZipFile.<init>(java.nio.channels.SeekableByteChannel)'
.
The solution for this is to install the Android Build Tools, version 33.0.2 . I don't know if newer versions work as
well, but you can have 33.0.2 installed in addition to other versions.
To install new build tools, go to Tools -> Android -> SDK Manager, SDK Tools tab, check "Show Package Details" at the bottom, select Build Tools version 33.0.2, and click Apply or OK. Then follow the installation steps.
Like the above error, this involves void org.apache.commons.compress.archivers.zip.ZipFile.<init>(java.nio.channels.SeekableByteChannel)'
.
This has to do with the placement of the robovm
plugin, which as of RoboVM 2.3.20 needs to be in ios/build.gradle
,
not the root build.gradle
. This is automatically fixed in projects generated by gdx-liftoff 1.12.0.1 and later, but
if you're for any reason using an older version, you can replace the top of ios/build.gradle
with:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "com.mobidevelop.robovm:robovm-gradle-plugin:$robovmVersion"
}
}
apply plugin: 'robovm'
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
(Starting with the line that sets encoding, everything else is the same.) You will also need to remove any mention of
robovm
plugin from the top of the root build.gradle
file; this means a line that starts with classpath
and a line
apply plugin: 'robovm'
.
A project that contains an Android module fails to run, saying Android Gradle plugin requires Java 17 to run.
Well, install Java 17 or higher and try again. You may need to set your JDK in your IDE. In IntelliJ IDEA, it's under
File -> Project Structure
; in that window, you can even download a JDK automatically in the SDKs tab by clicking +
and then Download JDK
. Android Studio doesn't provide as many ways to configure the JDK because it should include one
that works with the latest Android tools already.
I feel like I covered this one already...
A Kotlin project that contains an Android module gives the message jvm target compatibility should be set to the same Java version.
This should be fixed in 1.12.0.1 by using toolchains, or in 1.12.1.7 using Kotlin's jvmTarget
option; if that doesn't
work for you, here are other options.
The simplest solution here is to set your JDK to a Java 17 one and to change java.sourceCompatibility
and
java.targetCompatibility
to 11 each. You may need to set this for both Java and Kotlin, and they must use the same
versions across the board. You can also set the release
option to the same version as targetCompatibility
to help
with some incompatibilities between versions; this is only available if you are using Java 9 or later.
java.sourceCompatibility = 11
java.targetCompatibility = 11
if (JavaVersion.current().isJava9Compatible()) {
compileJava.options.release.set(11)
}
kotlin.compilerOptions.jvmTarget.set(JvmTarget.JVM_11)
Another solution is to use toolchains. In your root build.gradle, you can try adding
kotlin {
jvmToolchain(17)
}
(Probably adding it at the bottom of the file makes the most sense.) You still need to change sourceCompatibility
and
targetCompatibility
to the same version as your toolchain; these affect Java, while the toolchain should help Kotlin.
Running a Kotlin launcher via IDEA's or Android Studio's "green triangle" button in the margin fails.
This can be identified by an error message appearing such as:
Error: Could not find or load main class some.liftoff.project.lwjgl3.Lwjgl3Launcher
Caused by: java.lang.ClassNotFoundException: some.liftoff.project.lwjgl3.Lwjgl3Launcher
The simplest solution I've found for this is to try it again in exactly the same way. You actually can expect a different result! The first failure seems to set something necessary up in the IDE, and so later attempts work. If this still fails on later tries, you can try reimporting/refreshing/syncing the Gradle project (I don't know what each IDE calls it, but the button may look like two circling arrows in the Gradle sidebar, may be in a bar that pops up at the top, or may be a tiny button with just those circling arrows). Then try it again (twice, if needed) and see if it works.
Why does this work at all? 🤷♂️
Some steps were taken to try to address this in gdx-liftoff 1.12.1.5, but they can't seem to fully eliminate this problem on the very first run. They may be what fixes it for the second and later runs, though.
This is to be expected in 1.12.1.4, because some configuration was missing for Kotlin projects. That absence has been fixed in 1.12.1.5. In that version and in 1.12.1.6, Java also uses toolchains, the same way Kotlin does. This means there can be a long download for the first time you launch a gdx-liftoff project, but the download will get a JDK and keep it for any future projects to use (as long as they need the same version). This can be useful if someone wants to build your project but doesn't necessarily start with the right JVM version -- toolchains ensure they get the right one.
A good option for cross-platform building is to keep the language level on 11 (supported by everything except RoboVM). This works even on Android; even with its requirements for Java 17 in other places, using a toolchain JDK 11 seems to keep away from those requirements. A JDK 17 may still be needed for other parts of an Android build.
Version 1.12.1.7 does not use toolchains because some simpler ways to accomplish the same things became feasible.
First, ensure that you changed enableGraalNative=false
to enableGraalNative=true
in gradle.properties. This enables
the rest of the Graal code, including downloading dependencies once you re-sync the project.
On Windows, you also have to make sure a (rather recent) Visual Studio is installed and has C++ tools installed. The
C++ tools aren't checked by default when first installing Visual Studio. There may be issues with some non-US locales
when the Native Image tools try to locate Visual Studio programs/scripts. If you encounter these and have a
non-English-language and/or non-US locale, you may want to try this StackOverflow answer's solution.
You can also launch the Visual Studio x64 Native Tools command prompt (it's in the start menu by default), navigate to
your Liftoff project, and launch gradlew lwjgl3:nativeCompile
from there, which may work better.
The asset-location code was subtly broken in Liftoff 1.12.1.3 in some cases, namely when assets were in subfolders. This should be fixed in 1.12.1.4; if you are updating a 1.12.1.3 project that uses Graal Native Image, I recommend copying in the whole nativeimage.gradle file to replace the existing one (unless you have edited it) from either a new 1.12.1.4 or higher project or the same file from the generated demo.
There are probably a lot of ways Graal projects can have issues that I don't know about yet. There aren't many users for Graal Native Image in the Java gamedev space right now, but it's a good option for releasing small executables that are relatively hard to decompile (though not impossible). If more people start using Graal Native Image, this section will likely grow.
Liftoff 1.12.1.6 is the first to use GWT 2.11.0, which is almost entirely backwards-compatible... at an API level,
at least. It has different dependencies for gwt-user
, both because the version is newer, and that JAR is provided in
a different "group" -- org.gwtproject:gwt-user:2.11.0
instead of the older com.google.gwt:gwt-user:2.8.2
. This
isn't a problem in libGDX 1.13.0, since it uses GWT 2.11.0, but the fixes here won't
cause problems if they remain in a project updated to a newer libGDX version.
Before I start with the general solution, there's often a much easier one: for libraries that Liftoff has in its third-party extensions, any that need special treatment for GWT should already have that taken care of. The rest of this is only needed if a library you want to use isn't known to Liftoff and depends on an older GWT version.
Any library dependencies, not installed via a checkbox in Liftoff, that have code specifically for GWT, and depend
either directly on gwt-user (or gwt-dev) or indirectly via any version of com.badlogicgames.gdx:gdx-backend-gwt
before 1.13.0, can
trigger version conflicts from two different releases of GWT. That means if you are trying to use 2.11.0, but 2.10.1,
2.10.0, 2.9.0, 2.8.2, or 2.8.0 gets into the dependencies, because the groups are different, the older version won't be
replaced by the newer one. The solution is to exclude gwt-user (or the gdx-backend-gwt in the com.badlogicsgames.gdx
group) from any dependencies that themselves depend on the older GWT. You can identify which dependencies these are by
running the Gradle task gradlew html:dependencies
, and searching through it for com.google.gwt:gwt-user
. The output
you're trying to find looks like this:
+--- com.crashinvaders.basisu:basisu-gdx-gwt:1.0.0
| +--- com.crashinvaders.basisu:basisu-gdx:1.0.0 (*)
| \--- com.badlogicgames.gdx:gdx-backend-gwt:1.12.0
| +--- com.badlogicgames.gdx:gdx:1.12.0 -> 1.12.1 (*)
| \--- com.google.gwt:gwt-user:2.8.2
| +--- com.google.jsinterop:jsinterop-annotations:1.0.2 -> 2.0.2
| +--- javax.validation:validation-api:1.0.0.GA
| +--- javax.servlet:javax.servlet-api:3.1.0
| \--- org.w3c.css:sac:1.3
Right in the middle there is gwt-user 2.8.2 . Once you've found all the places that depend on old gwt-user, you'll need
to add an "exclude" to each dependency that was trying to obtain the old gwt-user. The new one should be kept; it's in
org.gwtproject:gwt-user
. The change to add an exclude block isn't too hard, but there's a lot of Gradle syntax
involved. You find an existing dependency, such as:
implementation "com.crashinvaders.basisu:basisu-gdx-gwt:$gdxBasisUniversalVersion:sources"
Then make sure the quoted String is also in parentheses (I don't know why this is needed), and after that add a curly brace block to configure that line.
implementation("com.crashinvaders.basisu:basisu-gdx-gwt:$gdxBasisUniversalVersion:sources") {
exclude group: "com.badlogicgames.gdx", module: "gdx-backend-gwt"
}
It is approximately equivalent to exclude the older gwt-user directly, and this may be better for some libraries that don't depend on libGDX, but do depend on GWT. This step could still be required even in the next version of libGDX, if libraries don't see updates. You could do that with this block instead of the one above:
implementation("com.crashinvaders.basisu:basisu-gdx-gwt:$gdxBasisUniversalVersion:sources") {
exclude group: "com.google.gwt", module: "gwt-user"
}
If a dependency has two parts that both depend on GWT, typically one with a :sources
classifier at the end and one
without :sources
, then both need to be given the same exclude block in curly braces.
Once every dependency on older GWT has been excluded, you should be able to run html:superDev
or html:dist
without
any issues. From this, anyway.
Libraries known to be affected by this include Artemis-ODB, the official Box2D, gdx-facebook, the official gdx-controllers, the unofficial gdx-controllerutils, Guacamole, and GdxBasisUniversal. There are probably more that are not in gdx-liftoff's known extension list. If you can get one of these using an extensions checkbox in Liftoff, that way is strongly recommended.
(There's probably a more elegant way to solve this using Gradle's dependency constraints; I just haven't figured it out yet... If anyone wants to flex their Gradle muscles and provide a way for the old gwt-user and gdx-backend-gwt dependencies to be automatically excluded, that would be very welcome!)
The iOS backend using Multi-OS Engine (MOE) seems to have some incompatibility with some Gradle versions, and they go back almost to 8.3. I'm not exactly clear on the nature of the incompatibility, other than it's supposed to be fixed in an upcoming Gradle release. Because that hadn't happened yet, MOE was temporarily removed from Liftoff 1.12.1.7; it won't work in earlier versions unless you go back to 1.12.0.4 or downgrade Gradle yourself to 8.3. Gradle 8.10 appears to work with MOE again, and the latest Liftoff releases (starting with 1.12.1.16) should have it.
This is all according to plan. The plan being, you should never be submitting an app to the Google Play Store with any default title, default description, or default icon, because that makes you look like a duplicate of any other app with those default properties. And duplicate apps get suspended for plagiarism! In order to at least avoid ever producing an identical icon, there are roughly 4 billion possible randomized icons that can be generated now. This involves randomly choosing some colors and two icons from OpenMoji, then chopping the icons in half, using a left and a right half, and chucking those icons into the Android app section, all poorly resized except for the hdpi one.
The iOS project doesn't currently use a randomized icon because it hasn't been reported to be a problem there, yet. If it does become a good idea to randomize iOS icons, it won't be hard to make them random, too.
In short, you should really pay attention to your icon when you are submitting to Google Play Store, but in case you don't one time, the awful random icon should not be currently in use by any actual apps.
While jpackage may be gone from Liftoff projects out of the box, in that box, in its place, is Construo! This is a
relatively new project by @fourlastor. It acts like the earlier Packr project, but
works in more places and can target most widely-used OS-architecture combinations (notably ARM Macs, such as those with
an M1 or newer processor, aren't supported by Packr but are supported by Construo). Like with Packr, and unlike with
jpackage, you can build for any target from any OS that can run Construo Gradle tasks. These tasks are typically grouped
under lwjgl3/construo/ , and the ones you actually want to run have names that start with "package".
Currently, that means lwjgl3:packageLinuxX64 , lwjgl3:packageMacM1 , lwjgl3:packageMacX64 , and lwjgl3:packageWinX64 .
Each of these will output a working executable for that OS in the lwjgl3/build/construo/
folder. For Windows, the
.exe is in the winX64/
subfolder, and a ready-to-upload .zip file is in dist/
. Other platforms place their
executables in different folders, such as linuxX64/
, but still place .zip files in dist/
. There will be several
other folders created in the process of shrinking down the release's size, but they usually won't affect you.
Construo has some uncommon advantages, such as how it tries to run on discrete graphics instead of integrated graphics
if both are available (such as on many laptops). It also has some pitfalls, though not many. It requires a Java 17 or
newer JDK to build the project, though not necessarily to run the resulting game, even if compiled to a JAR. It uses
jdeps
to determine which parts of the JDK to use, and jlink
to create a JRE without any parts the game doesn't use.
The jdeps/jlink step can go wrong if certain deprecated parts of the JDK are used, which is the case with the
commonly-used dependency VisUI up to its version 1.5.3 . You can change your dependency on VisUI to:
implementation "com.github.kotcrab.vis-ui:vis-ui:1aef382077"
Which gets a more-recent commit built by JitPack, and that more-recent code has a fix that makes it compatible with Construo (and with jlink in general). When VisUI releases a new stable version, Liftoff will update to that, and your project can as well. If that release is called 1.5.4 (which it might, but it could be 1.6.0 or even 2.0.0), use a dependency like this when the release is available:
implementation "com.kotcrab.vis:vis-ui:1.5.4"
// Note that this isn't available yet!
Other dependencies may also have issues, but no one has found them yet.
This was a bug in 1.12.1.13 and possibly some earlier versions, and it has been fixed in 1.12.1.14 (probably). It was caused by opening the file picker dialog on its own thread, which other OSes don't mind at all, but macOS can't handle at all. It causes a crash in native code (which can't be caught with try/catch). The fix turns out to be to just lock input to the rest of the Liftoff program as we were doing before, but permit the file dialog to do its thing, and re-enable input when the dialog closes.
1.12.1.15 and up use a newer version of the file dialog library (we went from LWJGL 3.3.1's NFD binding to LWJGL 3.3.4's NFDe binding). This may have some bug fixes, and it may have all kinds of new bugs, but at the very least it's more modern than the rather old, sometimes-buggy NFD version we were using.
First off, if you can run a .jar file normally, you should try that before any of the following steps. The runnable .jar file is always provided with Liftoff releases. The native distribution is meant for cases when you don't have a JDK installed at the system level (such as if your IDE has its own JDK, and you haven't added one).
This is a bit of a tricky issue, because the native distributions are built (currently) on Windows 11, which doesn't
have the same ways it can mark files as macOS. Liftoff version 1.12.1.15 and newer does mark the right file as
executable, but still has to deal with how macOS browsers like to put downloaded files in "quarantine."
Once you have downloaded the macOS M1 or macOS x64 distribution, you can (on macOS) run this command from the same
directory as gdx-liftoff.app
:
xattr -cr gdx-liftoff.app
This removes gdx-liftoff.app
from quarantine. After running that command, the .app should be normally runnable via
double-click. If that doesn't work, you may have an older Liftoff distribution (1.12.1.14 or older), which if you need
to use that version for some reason, needs an extra step after the above one:
chmod +x gdx-liftoff.app/Contents/MacOS/gdx-liftoff
These were found by JojoIce in this issue thread;
more future discoveries could be in that thread or in other issues. As far as I can tell, the first command clears any
attributes on the downloaded .app
, which removes any quarantine settings, and the second command sets the entry point
inside the .app
to be executable. The second command isn't needed anymore thanks to changes in Liftoff 1.12.1.15,
which uses construo 1.4.1 (the current version uses 1.5.1). Since the quarantine settings are applied to the .app when it is downloaded, there
isn't really any change that could be made to how Liftoff is built that would make it somehow avoid being quarantined.
The only ways I know of that handle quarantine settings are to a) download gdx-liftoff.app via a tool such as curl, or
b) download it however you want and use the above xattr
command to remove the file from quarantine. Using curl will
simply not apply quarantine to any files it downloads, but I haven't used it in a while (and never on recent macOS), so
the exact command-line syntax is something you'd have to look up.
These have been reported intermittently with Gradle 8.10.1, which Liftoff 1.12.1.15 uses, but that version of Liftoff also includes one of the recommended workarounds for that Gradle version - disabling Gradle's daemon service. It is entirely likely that the way the daemon is disabled might not work, because Gradle really likes to try to use the daemon even when it is broken. Disabling the daemon doesn't appear to have much performance impact, especially since Gradle 8.10.1 is mostly meant to fix a performance regression in Gradle 8.10, and so could be faster either with or without the daemon. If you are encountering this, you should upgrade Gradle to 8.10.2 or (better) 8.11.1, either of which fixes the bug:
- Open the file
gradle/wrapper/gradle-wrapper.properties
in your project. - Find where it has the version string
8.10.1
; this is on a line that starts withdistributionUrl
. - Change
8.10.1
to8.11.1
. - Save and reload your Gradle project.
- If you don't know how to reload your Gradle project, it is done automatically by Android Studio, so you don't need to do anything but save there. Otherwise, in IDEA:
- Click the sideways
Gradle
tab on the right side of the window. - Click the two circling arrows ("Reload All Gradle Projects") in the pane that appears.
- This clears out most IDE-specific configuration you may have done via "Project Structure"; that configuration is meant to be controlled by Gradle, so Gradle changes will take priority. Usually Project Structure changes beyond the JDK setting aren't needed, and the JDK settings won't be cleared out.
The Multi-OS Engine target, which lets you build libGDX games/apps for iOS using a different method than RoboVM, wasn't
working for a while, and was removed from Liftoff's options while any combination of Gradle, Gradle plugins, and MOE
itself were experiencing bugs. This should be resolved as of 1.12.1.16 1.12.1.17 ! You can now select MOE in the Platforms
selection box. How well could it work? That depends on what you want to do with it. You need a macOS machine at some
point to deploy to iOS, but unlike RoboVM, MOE should be able to allow you to write code on Windows or Linux and send it
to a (possibly remote) macOS machine to build it.
The latest MOE release posting is here, and that forum
is also where to go for MOE-specific questions.