gdx-teavm Usage Guide

July 23, 2026 ยท View on GitHub

This guide covers the two supported ways to build with gdx-teavm:

  • The Gradle plugin, recommended for normal application projects.
  • The manual builder API, useful for custom build launchers and backend development.

Repositories

For releases:

repositories {
    mavenCentral()
}

For snapshots:

repositories {
    mavenCentral()
    maven("https://central.sonatype.com/repository/maven-snapshots/")
}

If Gradle cannot resolve TeaVM artifacts from Central, add the TeaVM repository:

maven("https://teavm.org/maven/repository/")

For plugin resolution, put the repositories in pluginManagement in settings.gradle.kts:

pluginManagement {
    repositories {
        mavenCentral()
        maven("https://central.sonatype.com/repository/maven-snapshots/")
    }
}

The gdx-teavm plugin marker is published to Maven with the library artifacts, so Gradle Plugin Portal is not required for this plugin.

Apply the plugin to the module that should produce TeaVM output.

plugins {
    id("com.github.xpenatan.gdx-teavm") version "<latest-release>"
}

For normal projects, always use the latest released version listed in the README status table. Use -SNAPSHOT only when you need work-in-progress changes; snapshot builds also require the snapshot repository configured above.

The extension block is named gdxTeaVM.

gdxTeaVM {
    assets("assets")
    classpathAssets("com/example/game/assets")
    reflection("com.example.game.save**")

    js {
        mainClass.set("com.example.game.teavm.WebLauncher")
    }

    wasm {
        mainClass.set("com.example.game.teavm.WebLauncher")
    }
}

Automatic Backend Dependencies

The plugin adds the required backend dependency for every declared target. For example, js {} or wasm {} adds backend-web, glfw {} adds backend-glfw, ios {} adds backend-ios, and android {} adds backend-android.

For regular Java modules, those dependencies are added to both implementation and TeaVM's generation configuration. This means a standalone plugin module can compile launchers that import WebApplication or GLFWApplication without manually declaring the backend artifacts.

Android uses a dedicated integration path: backend-android is added to TeaVM's configuration, and the plugin registers generated runtime bridge sources with the Android compile. The Android Gradle Plugin owns APK packaging, install tasks, build types, signing, manifests, resources, and native CMake execution. Apply gdx-teavm to a real Android application module and declare an android {} target there; the plugin generates the TeaVM C/CMake payload, while Android Gradle tasks build and install the APK.

Web Targets

Use js {} for JavaScript and wasm {} for Wasm.

import org.teavm.gradle.api.OptimizationLevel

gdxTeaVM {
    assets("assets")

    js {
        mainClass.set("com.example.game.teavm.WebLauncher")
        htmlTitle.set("My Game")
        htmlWidth.set(1280)
        htmlHeight.set(720)
        serverPort.set(8080)
        targetFileName.set("app.js")
        optimization.set(OptimizationLevel.BALANCED)
        obfuscated.set(true)
        devServer {
            enabled.set(true)
            autoBuild.set(true)
            autoReload.set(true)
        }
    }

    wasm {
        mainClass.set("com.example.game.teavm.WebLauncher")
        htmlTitle.set("My Game")
        htmlWidth.set(1280)
        htmlHeight.set(720)
        serverPort.set(8080)
        targetFileName.set("app.wasm")
        optimization.set(OptimizationLevel.AGGRESSIVE)
        obfuscated.set(true)
        copyRuntime.set(true)
        modularRuntime.set(false)
        devServer {
            enabled.set(true)
            autoBuild.set(true)
            autoReload.set(true)
        }
    }
}

Generated tasks:

TaskDescription
gdx_teavm_web_js_buildBuild the JavaScript web app
gdx_teavm_web_js_runBuild and serve the JavaScript web app, or use TeaVM's JS dev server when enabled
gdx_teavm_web_wasm_buildBuild the Wasm web app
gdx_teavm_web_wasm_runBuild and serve the Wasm web app, or use TeaVM's WasmGC dev server when enabled

By default, each run task builds its target and serves the result with JettyServer. Setting devServer.enabled to true, as shown above, makes the same task and URL use TeaVM's persistent development server instead.

autoBuild defaults to true and rebuilds after source, resource, asset, or static-file changes. Set it to false for a serve-only session. autoReload independently refreshes connected pages after successful rebuilds. Compilation failures leave the previous application running while the task waits for another change.

The development server supplies the source maps, Java sources, and debug metadata used by browser tools; the corresponding target properties still control normal build output. Keep the run task active for the development session and stop it with Ctrl+C or the IDE stop action. See the development-server property reference for all options.

Checking Browser Debugging

  1. Run ./gradlew gdx_teavm_web_js_run or ./gradlew gdx_teavm_web_wasm_run.
  2. Open http://localhost:8080, using the configured serverPort if it differs.
  3. Open the browser's developer tools, find the Java launcher under Sources, set a breakpoint, and reload the page.
  4. Edit and save a project file. The active run task rebuilds it; autoReload.set(true) also refreshes the page.
  5. Stop the active Gradle run task with Ctrl+C, or the IDE's stop action, when finished.

The development server exposes Java sources and mappings to browser developer tools. Browser developer tools remain the common debugging path for Wasm.

Debugging JavaScript in IntelliJ IDEA

TeaVM's IntelliJ debugger supports the JavaScript target, not Wasm. The Gradle run task continues to own compilation and the application server; IntelliJ's TeaVM debug server is only the bridge between IntelliJ and Chrome.

  1. Install TeaVM Integration in IntelliJ IDEA and install the TeaVM debugger agent in Chrome.
  2. Run gdx_teavm_web_js_run normally and leave the Gradle task active. Do not create a TeaVM development-server configuration in IntelliJ because the Gradle task already provides it.
  3. In Run > Edit Configurations, add TeaVM debug server, leave its listen port at 2357, and start that configuration with IntelliJ's Debug action.
  4. Open the application URL in Chrome, click the TeaVM debugger-agent teapot icon to attach the tab, set a breakpoint in Java source, and reload the page.
  5. IntelliJ should stop at the Java source location. If the breakpoint does not bind, verify that <application-url>/app.js.teavmdbg returns HTTP 200, then attach the Chrome agent again and reload.

TeaVM debugging provides Java source breakpoints, stepping, fields, and mapped local variables where metadata is available. Generated app.js frames and js: temporary variables can still appear, and Java expression watches are more limited than in a normal JVM debug session.

Native Targets

Declare glfw {} for desktop native backend tasks. An experimental ios {} block is also available for WIP TeaVM C payloads. Each native target block contains its own TeaVM C settings, so plugin targets can keep different launcher classes, heap sizes, optimization levels, and backend-specific options.

import org.teavm.gradle.api.OptimizationLevel

gdxTeaVM {
    assets("assets")

    glfw {
        mainClass.set("com.example.game.teavm.GlfwLauncher")
        optimization.set(OptimizationLevel.AGGRESSIVE)
        minHeapSizeMb.set(64)
        maxHeapSizeMb.set(512)
        buildType.set("Debug")
        consoleLog.set(false)
    }

    ios {
        mainClass.set("com.example.game.teavm.IosLauncher")
        optimization.set(OptimizationLevel.NONE)
        xcodeProjectDir.set(layout.buildDirectory.dir("dist/ios/xcode"))
    }
}

Generated native plugin tasks:

TaskDescription
gdx_teavm_glfw_generateGenerate the C project
gdx_teavm_glfw_buildGenerate and build using glfw.buildType
gdx_teavm_glfw_runGenerate, build, and run using glfw.buildType
gdx_teavm_ios_generateGenerate the experimental iOS C/assets
gdx_teavm_ios_prepare_angleDownload and extract the MetalANGLEKit frameworks used by ios.graphicsApi=angle
gdx_teavm_ios_init_xcodeCreate the experimental iOS Xcode project if missing
gdx_teavm_ios_regenerate_xcodeRegenerate the experimental iOS Xcode project, overwriting manual project edits
gdx_teavm_ios_open_xcodeCreate the experimental iOS Xcode project if missing and open it in Xcode
gdx_teavm_ios_build_simulatorGenerate and build the experimental iOS app for the simulator
gdx_teavm_ios_run_simulatorGenerate, build, install, and launch the experimental iOS app on a simulator

Android Target

Android builds use a real Android application module, not a generated Android project. The Android module applies both com.android.application and com.github.xpenatan.gdx-teavm, owns normal Android settings, and points Android's external native build at the generated TeaVM CMake project.

The TeaVM launcher lives in src/native/java so it is used for TeaVM C generation. The gdx-teavm plugin registers this folder with Android's main Java source model so Android Studio imports it as source code, mirrors the teavm dependencies onto Android's compile-only IDE classpath, and excludes the folder from Android's APK Java compile. This keeps imports and completion working after a Gradle sync without packaging the launcher into the APK.

The Android runtime bridge is provided by backend-android. The plugin adds generated Java sources for com.github.xpenatan.gdx.teavm.android.TeaAndroidActivity and TeaAndroidView to the Android app compile, so projects can use a minimal Activity subclass for the default full-screen app case.

During Gradle configuration, the plugin also writes a tiny bootstrap CMakeLists.txt into build/generated/gdx-teavm/android when the real TeaVM native payload is not there yet. This lets Android Studio sync a fresh project before gdx_teavm_android_generate has run. Normal Android build tasks still replace that bootstrap project with the real TeaVM-generated C/CMake output before compilation.

plugins {
    id("com.android.application")
    id("com.github.xpenatan.gdx-teavm")
}

val generatedAndroidCMakeFile = layout.buildDirectory.file("generated/gdx-teavm/android/CMakeLists.txt")
val androidCxxDir = rootProject.layout.buildDirectory.dir("android-cxx/my-game-android")

android {
    namespace = "com.example.game.android"
    compileSdk = 36

    defaultConfig {
        applicationId = "com.example.game.android"
        minSdk = 21
        targetSdk = 36
    }

    splits {
        abi {
            isEnable = true
            reset()
            include("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
            isUniversalApk = false
        }
    }

    externalNativeBuild {
        cmake {
            path = generatedAndroidCMakeFile.get().asFile
            buildStagingDirectory = androidCxxDir.get().asFile
        }
    }

    sourceSets {
        getByName("main") {
            assets.srcDir(file("../assets"))
        }
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
}

dependencies {
    add("teavm", project(":core"))
}

gdxTeaVM {
    android {
        mainClass.set("com.example.game.teavm.AndroidLauncher")
    }
}

Generated Android plugin task:

TaskDescription
gdx_teavm_android_generateGenerate the TeaVM C/CMake payload for the Android module

There is no gdx_teavm_android_run task. Android Debug, Release, install, launch, signing, and emulator/device behavior use standard Android Gradle Plugin and ADB tooling.

Common Android tasks:

TaskDescription
assembleDebugGenerate TeaVM C, build native Debug code, and package a debug APK
assembleReleaseGenerate TeaVM C, build native Release code, and package release APKs
installDebugBuild and install the debug APK on the visible device or emulator

The Android module should point assets at the normal asset source directory. The gdx-teavm Android generation step does not copy game assets into the generated native payload.

Default Activity subclass:

package com.example.game.android;

import com.github.xpenatan.gdx.teavm.android.TeaAndroidActivity;

public class MainActivity extends TeaAndroidActivity {
}

Manifest entry:

<activity
    android:name="com.example.game.android.MainActivity"
    android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize"
    android:exported="true"
    android:screenOrientation="landscape">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

For embedded usage, add TeaAndroidView to your own Activity or layout instead of using the default full-screen Activity:

TeaAndroidView gdxView = new TeaAndroidView(this);
container.addView(gdxView);

Forward the host Activity lifecycle to the view:

@Override
protected void onPause() {
    gdxView.onPause();
    super.onPause();
}

@Override
protected void onResume() {
    super.onResume();
    gdxView.onResume();
}

@Override
protected void onDestroy() {
    gdxView.dispose();
    super.onDestroy();
}

Only one active TeaAndroidView is supported per process for now because libGDX globals such as Gdx.app, Gdx.graphics, and the TeaVM Android application instance are process-global.

Testing The Basic Android Example

Start a visible emulator from Android Studio Device Manager, or plug in a real Android device. Then verify that ADB sees one device in the device state:

adb devices -l

Build the debug APK:

./gradlew :examples:basic:platforms:android:assembleDebug

Install it on the connected device or running emulator:

./gradlew :examples:basic:platforms:android:installDebug

Launch it from the Android launcher as gdx-teavm basic, or start the Activity directly with ADB:

adb shell am start -n com.github.xpenatan.gdx.teavm.examples.basic.android/com.github.xpenatan.gdx.teavm.examples.basic.android.MainActivity

Build the release APKs:

./gradlew :examples:basic:platforms:android:assembleRelease

The basic Android demo enables ABI APK splits and disables the universal APK. Debug and release outputs use one APK per ABI:

Build typeABIAPK
Debugarm64-v8aexamples/basic/platforms/android/build/outputs/apk/debug/android-arm64-v8a-debug.apk
Debugarmeabi-v7aexamples/basic/platforms/android/build/outputs/apk/debug/android-armeabi-v7a-debug.apk
Debugx86examples/basic/platforms/android/build/outputs/apk/debug/android-x86-debug.apk
Debugx86_64examples/basic/platforms/android/build/outputs/apk/debug/android-x86_64-debug.apk
Releasearm64-v8aexamples/basic/platforms/android/build/outputs/apk/release/android-arm64-v8a-release.apk
Releasearmeabi-v7aexamples/basic/platforms/android/build/outputs/apk/release/android-armeabi-v7a-release.apk
Releasex86examples/basic/platforms/android/build/outputs/apk/release/android-x86-release.apk
Releasex86_64examples/basic/platforms/android/build/outputs/apk/release/android-x86_64-release.apk

For Play Store distribution, prefer bundleRelease and upload the generated Android App Bundle so Play can serve the right ABI split to each device:

./gradlew :examples:basic:platforms:android:bundleRelease

Generated TeaVM C output is written under examples/basic/platforms/android/build/generated/gdx-teavm/android. Android CMake/Ninja staging is configured under the root build folder at build/android-cxx/examples-basic-platforms-android so AGP's native intermediates do not create an untracked .cxx folder in the Android module root.

The Android app module uses Java 8 source and target compatibility for APK-side Java code. This avoids Android Gradle Plugin's Java 9+ androidJdkImage transform, which can fail when Gradle is running on newer JDKs. The TeaVM launcher source in src/native/java is compiled separately for TeaVM generation.

Plugin Properties

See the plugin property reference for every gdxTeaVM property, grouped by shared settings and by target backend.

Manual Builder API

The builder API is based on TeaBuilder and a concrete backend.

Web Builder

dependencies {
    implementation("com.github.xpenatan.gdx-teavm:backend-web:-SNAPSHOT")
    implementation(project(":core"))
}
import com.github.xpenatan.gdx.teavm.backends.shared.config.AssetFileHandle;
import com.github.xpenatan.gdx.teavm.backends.shared.config.builder.TeaBuilder;
import com.github.xpenatan.gdx.teavm.backends.web.config.backend.WebBackend;
import java.io.File;
import org.teavm.vm.TeaVMOptimizationLevel;

public class BuildWeb {
    public static void main(String[] args) {
        WebBackend backend = new WebBackend()
                .setHtmlTitle("My Game")
                .setHtmlWidth(1280)
                .setHtmlHeight(720)
                .setLogoPath("images/loading.png")
                .setStartJettyAfterBuild(true);

        new TeaBuilder(backend)
                .addAssets(new AssetFileHandle("assets"))
                .setMainClass("com.example.game.teavm.WebLauncher")
                .setOutputName("app")
                .setOptimizationLevel(TeaVMOptimizationLevel.SIMPLE)
                .setObfuscated(false)
                .build(new File("build/dist"));
    }
}

setLogoPath(...) sets the default asset path requested by WebPreloadApplicationListener. Assets added with TeaBuilder.addAssets(...) are copied by the regular asset pipeline. When copyLoadingAsset is enabled, the builder also tries to copy logoPath as a classpath resource. A custom preload listener can override the compiled default without changing the builder configuration:

WebPreloadApplicationListener preload = new WebPreloadApplicationListener();
preload.startupLogo = "images/alternate-loading.png";
new WebApplication(game, preload, config);

For Wasm with the builder:

WebBackend backend = new WebBackend()
        .setWebAssembly(true)
        .setStartJettyAfterBuild(true);

GLFW Builder

dependencies {
    implementation("com.github.xpenatan.gdx-teavm:backend-glfw:-SNAPSHOT")
    implementation(project(":core"))
}
import com.github.xpenatan.gdx.teavm.backends.glfw.config.backend.TeaGLFWBackend;
import com.github.xpenatan.gdx.teavm.backends.shared.config.AssetFileHandle;
import com.github.xpenatan.gdx.teavm.backends.shared.config.builder.TeaBuilder;
import java.io.File;
import org.teavm.vm.TeaVMOptimizationLevel;

public class BuildGlfw {
    public static void main(String[] args) {
        TeaGLFWBackend backend = new TeaGLFWBackend()
                .setBuildType(TeaGLFWBackend.NativeBuildType.DEBUG)
                .setBuildExecutableAfterBuild(false)
                .setRunExecutableAfterBuild(false);

        new TeaBuilder(backend)
                .addAssets(new AssetFileHandle("assets"))
                .setMainClass("com.example.game.teavm.GlfwLauncher")
                .setOptimizationLevel(TeaVMOptimizationLevel.FULL)
                .setMinHeapSize(64 * 1024 * 1024)
                .setMaxHeapSize(512 * 1024 * 1024)
                .setMinDirectBuffersSize(64 * 1024 * 1024)
                .build(new File("build/dist"));
    }
}

iOS Plugin Target

iOS is experimental. The Gradle plugin exposes ios {} for TeaVM C/assets generation, Xcode project initialization, and simulator build/run tasks. Xcode and simulator tasks require macOS.

gdxTeaVM {
    ios {
        mainClass.set("com.example.game.teavm.IosLauncher")
        graphicsApi.set("angle")
    }
}

Use gdx_teavm_ios_init_xcode to create the Xcode project from the templates under backend-ios. The default graphicsApi is angle, which downloads libGDX's pinned MetalANGLEKit bundle and renders GLES through Metal. Set graphicsApi to gles to generate the older native OpenGL ES / GLKit project.

Assets

All build paths share the same asset planner/copy logic.

  • Disk assets are copied as libGDX internal assets.
  • Classpath assets are copied and marked as classpath assets in the generated preload manifest.
  • Web targets compile the preload manifest into the TeaVM output through a generated runtime class instead of writing an assets/preload.txt file.
  • Native targets copy assets directly and do not create a preload manifest file.
  • The manifest stores enough file type information for runtime loading to preserve Gdx.files.internal(...) and Gdx.files.classpath(...) behavior.

Builder examples:

teaBuilder.addAssets(new AssetFileHandle("assets"));
teaBuilder.addAssets(new AssetFileHandle("com/example/game/skin", FileType.Classpath));

Plugin examples:

gdxTeaVM {
    assets("assets")
    classpathAssets("com/example/game/skin")
}

Libraries can contribute runtime files with META-INF/gdx-teavm.properties:

resources=runtime/file.js
classpath-resources=com/example/library/shaders
ignore-resources=unused/file.txt

Reflection

TeaVM needs ahead-of-time metadata for reflection. gdx-teavm generates the metadata used by the libGDX reflection emulation classes.

Builder:

new TeaBuilder(backend)
        .addReflectionClass("com.example.game.save**")
        .addReflectionClass("com.badlogic.gdx.math.Vector2");

Plugin:

gdxTeaVM {
    reflection("com.example.game.save**")
    reflectionDebug.set(false)
}

Use concrete class names for individual types and package patterns ending in ** for package trees.