Troubleshooting
This page covers the most common problems you may run into while building or running a FlixelGDX game — from first-time setup stumbles to runtime surprises to the pitfalls of GraalVM native-image compilation. Each section is self-contained; jump straight to the heading that matches your problem.
Setup and environment
Java version too old
FlixelGDX requires Java 17 or later. If Gradle reports an error like:
error: source release 17 requires target release 17
or your IDE shows a red underline on record syntax, switch statements, or text blocks, your project is compiling against an older JDK. Fix it in two places:
- Gradle toolchain — in each subproject's
build.gradle:
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
- IDE SDK — point the project SDK at a JDK 17+ installation. In IntelliJ: File > Project Structure > Project > SDK.
To check what is currently active: ./gradlew -version prints the JVM Gradle is using;
java -version in a terminal prints the system default.
Gradle sync errors after cloning
If ./gradlew build fails with something like:
Could not resolve com.github.flixelgdx:flixelgdx:<version>
JitPack needs to build the dependency on its first request. This normally succeeds within a minute.
Check the JitPack build log for your tag at https://jitpack.io/com/github/flixelgdx/flixelgdx/.
If the build failed, use the Rebuild button there. While you wait, your Gradle build will keep
retrying on --refresh-dependencies.
If the error persists, verify that your settings.gradle has JitPack in the dependencyResolutionManagement block:
dependencyResolutionManagement {
repositories {
maven { url 'https://jitpack.io' }
mavenCentral()
// ...
}
}
Could not find tools.jar (Windows)
This warning appears when Gradle cannot find the JDK's tools.jar. It is harmless on Java 9+ (the
tools.jar API was merged into the JDK itself), but it usually means your JAVA_HOME environment
variable points at a JRE directory rather than a JDK. Set it to your JDK root, for example:
JAVA_HOME=C:\Program Files\Eclipse Adoptium\jdk-17.0.11.9-hotspot
Hot-reload / automatic restart not working
FlixelGDX does not have built-in hot-reload. Use the debug overlay's resetState command (or a
custom command that calls Flixel.switchState) to re-enter a state without restarting the process.
For Java code changes you still need to restart the JVM.
Runtime errors
Do I need to null-check Flixel.debug?
No. Flixel.debug and Flixel.watch are created during startup and are present in every build — they are
never null. What changes between DEBUG and RELEASE is whether a visual overlay is attached. In RELEASE
mode their methods become harmless no-ops: a command you register has no console to appear in, and watch suppliers
are never evaluated, so there is no per-frame cost. You can call them unconditionally:
// Safe in every build -- no null guard required. Do this in your state's create().
Flixel.debug.registerCommand("spawn", args -> spawnEnemy());
The managers are wired up by Flixel.initialize(...) (called by the launcher), so they are ready from your first
state's create() onward. The only place they can still be null is inside your FlixelGame subclass's own
constructor, which runs before initialization — register commands from create(), not the game constructor.
If you want to skip registering debug-only commands in a shipped build (purely to save the tiny memory they occupy), gate them behind your own flag rather than a null check:
private static final boolean DEV = true; // set to false before shipping
if (DEV) {
Flixel.debug.registerCommand("spawn", args -> spawnEnemy());
}
State does not transition / switchState seems to do nothing
Flixel.switchState happens immediately and synchronously: the moment you call it, the current state is
destroy()ed and the new state's create() runs right there in the call. (By default it also cancels all active
tweens and triggers a garbage-collection pass.) If a switch "does nothing," the usual causes are:
- An exception during the new state's
create(). Ifcreate()throws, the new state is left half-built. Check the console / Log window for a redERRORentry. - You kept using the old state after switching. Because the switch is immediate, any code that runs after
switchState(...)in the sameupdate()is touching a state that has already been destroyed. CallswitchState(...)last in your update logic, orreturnright after it, so you do not read or mutate fields that no longer exist.
Objects are not rendering
Common causes:
- Not added to the state.
new FlixelSprite(...)alone does nothing — calladd(sprite)in your state'screate(). - Off-screen. The camera is centered on world position
(0, 0)by default. An object at(5000, 5000)is off camera. UseFlixel.debug.setDrawDebug(true)to draw hitboxes and see where everything actually is. - Alpha is 0. A sprite's alpha (read with
getAlpha(), changed withsetAlpha(float)) defaults to1f. A fully transparent color passed tomakeGraphicalso renders nothing. - Wrong layer. Groups and sub-states render in insertion order. An opaque sprite added first will sit behind one added second.
Collision / Flixel.collide() is not separating objects
- At least one object must be movable. Call
setImmovable(false)on the object you want pushed apart. If both objects are immovable (setImmovable(true)), the notify callback still fires but neither object is separated. - An object only collides while it is solid.
setSolid(false)(orsetAllowCollisions(FlixelDirectionFlags.NONE)) makes it pass straight through everything. The default isFlixelDirectionFlags.ANY, which collides on all sides.FlixelDirectionFlagslives inorg.flixelgdx.util. Flixel.collide()separates by moving positions. If you also move an object by hand (setX(...)/setY(...)) in the same frame, the two can fight each other and cause jitter. Prefer driving movement throughsetVelocity(...)and letting collision do the separating.
Audio does not play
- File format. Every backend (excluding web/TeaVM) is powered by gdx-miniaudio.
.mp3,.oggVorbis/Opus,.wavand.flacfiles are the default supported audio formats — any other format will not work. - Path is wrong. Asset paths are relative to the
assets/folder at the root of theassetsmodule (or wherever yourassetssource root points). Pass the path without a leading slash. - TeaVM. Web builds use the Web Audio API. Audio autoplay is blocked by browsers until the
user interacts with the page.
Flixel.sound.play(...)called before a click will silently fail in most browsers.
Tweens stop before finishing
- Calling
tween.cancel()orFlixelTween.cancelTweensOf(target)early will stop them. - If the owning state is destroyed mid-tween, all tweens are automatically cancelled.
Watch panel shows null or stale values
Watch entries are refreshed at ~10Hz (roughly ten times per second) while the overlay is visible. If the overlay is hidden (F2 toggled off), suppliers are not called. Values shown when you reopen the overlay are from the first refresh after it became visible, which should be up to date.
If a supplier returns null, confirm the object it references is not null itself — player.getX()
will throw a NullPointerException if player is null, which the watch panel silently catches
and shows as <error>.
TeaVM (browser) builds
ClassNotFoundException at TeaVM compile time
TeaVM traces the reachability of every class at compile time. If it cannot find a class referenced by your code, the build fails. Common causes:
- A dependency that uses reflection without TeaVM metadata (
@TeaVMPropertiesorClassHolderTransformer). Check if the library has a TeaVM-compatible build. - Calling
Class.forName(...)with a string literal. Replace it with a direct class reference or annotate the class with@TeaVMInclude.
GraalVM native image
GraalVM's native-image tool compiles your game to a self-contained native executable. This is
great for distribution — no JVM required — but it is strict about things the regular JVM is lenient
about: reflection, JNI, resource loading, and dynamic class loading all require explicit
configuration.
The project generator already wires up the generateNativeConfig task and the FlixelGraalFeature
auto-registration, so most configuration is handled for you. But if you add libraries or use
reflection yourself, you may hit issues described below.
generateNativeConfig firstBefore trying to build a native image, launch your game in a standard JVM run with all features
exercised and let generateNativeConfig collect reflection/JNI/resource usage at runtime. This
produces META-INF/native-image/ config files that native-image reads automatically. See
your project's README for the exact Gradle task name.
java.lang.reflect.InaccessibleObjectException in native image
This error means native-image found a reflective access at runtime that was not declared in
reflect-config.json. Run generateNativeConfig (described above), then rebuild. If the error
is from a third-party library, check whether that library ships its own native-image metadata
under META-INF/native-image/.
UnsatisfiedLinkError for native libraries (JNI)
Native-image bundles JNI libraries differently from a regular JVM run. The linker must know in advance which classes and fields cross the JNI boundary.
imgui-java specifically calls GetFieldID() inside its nInitJni() function. GraalVM does not
automatically register those fields, so it returns null for them at runtime, which causes imgui
to crash or behave incorrectly.
The project generator includes jni-config.json entries for the imgui classes that need them. If
you upgraded imgui-java and the crash returns, re-run generateNativeConfig with --agentlib: native-image-agent so the agent captures the new GetFieldID calls, then merge the output back
into your jni-config.json.
You also need the imgui classes to be runtime-initialized rather than build-time initialized, because imgui's static initializers start the native bridge:
--initialize-at-run-time=imgui.ImGui,imgui.internal.ImGui
The FlixelGraalFeature class registers these for you when it is on the classpath. If you are
not using the project generator's template, add this line to your native-image.properties or
build.gradle native-image args.
Resources not found at runtime
Static resource files (images, audio, JSON) must be declared in resource-config.json so
native-image includes them in the binary. Pattern:
{
"resources": {
"includes": [
{ "pattern": ".*\\.png" },
{ "pattern": ".*\\.ogg" },
{ "pattern": ".*\\.json" }
]
}
}
The generateNativeConfig task captures resource accesses that happen during a real run, but only
for resources that were actually loaded during that session. If a level, sound, or atlas is only
loaded in a later stage that you did not reach during config generation, it will be missing. Either
exercise that code path during config generation, or add the pattern manually.
native-image build fails with OutOfMemoryError
This is the compiler's memory, not your game's. The native-image tool performs whole-program
static analysis — it traces every reachable class, builds a call graph, and AOT-compiles everything
to a native binary. That process is genuinely memory-intensive at build time. Your finished native
executable still runs on the usual small heap (typically 4-8 MB for a FlixelGDX game).
For mid-size games, the compiler typically needs 4-8 GB. Set the compiler JVM heap via the Gradle
graalvmNative block:
graalvmNative {
binaries {
main {
jvmArgs.add('-Xmx8g')
}
}
}
If the build still runs out of memory, add -Ob (quick-build mode) while iterating — it skips the
heaviest optimizations and cuts peak memory significantly. Remove it for your final release build.
Slow startup on first native launch (PGO)
The first run of a fresh native image is slightly slower than subsequent runs because GraalVM's Profile-Guided Optimization (PGO) data is not yet warm. For a properly shipped build this is rarely noticeable for games. If you are benchmarking, run the binary at least once to warm it, or include a PGO profile during the build.
The JVMCI error on Temurin (non-GraalVM JDK)
If you launch the native-image build with a standard JDK (e.g., Eclipse Temurin) rather than
a GraalVM distribution, the build may fail with:
Module java.base does not export ... to module ...
or a JVMCI-related error on startup. native-image is a GraalVM tool and must be run with a
GraalVM JDK. Install GraalVM 21+ and set GRAALVM_HOME (or set the Gradle toolchain to use it):
graalvmNative {
toolchainDetection = true
}
The ordinary game JAR runs fine on any JDK 17+; the restriction only applies when compiling native images.
"Image generation has failed" with no clear error
Run the build with -H:+ReportExceptionStackTraces to get the full stack trace. Add it to
buildArgs in your Gradle config:
graalvmNative {
binaries {
main {
buildArgs.add('-H:+ReportExceptionStackTraces')
buildArgs.add('--verbose')
}
}
}
--verbose shows which class or feature triggered the failure. Most "silent" failures
are caused by a missing class registration (reflection or JNI), a missing resource pattern, or
an initializer that tried to access disk/network at build time.
Debug builds vs release builds for native image
During development, build with -Ob (quick build mode) to skip heavy optimizations and get faster
iteration:
graalvmNative {
binaries {
main {
buildArgs.add('-Ob')
}
}
}
Remove -Ob for your release build to get the full performance benefit of native compilation.
Getting more help
If none of the above solves your problem:
- Check the Log window (if you are in debug mode) — red
ERRORentries almost always point directly at the cause. - Run with
Flixel.log.startFileLogging()and share the log file. - Open an issue at the FlixelGDX repository with your stack trace and a minimal reproduction.