libGDX Headless Backend
Reference for running libGDX without a display, audio, or input — for servers, CI/testing, procedural generation, and headless simulation. Lives in the gdx-backend-headless module.
Setup
import com.badlogic.gdx.backends.headless.HeadlessApplication;
import com.badlogic.gdx.backends.headless.HeadlessApplicationConfiguration;
public class ServerLauncher {
public static void main(String[] args) {
HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration();
config.updatesPerSecond = 20; // render() called 20 times/sec
new HeadlessApplication(new MyServerListener(), config);
}
}
Dependency (Gradle):
implementation "com.badlogicgames.gdx:gdx-backend-headless:$gdxVersion"
implementation "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop"
HeadlessApplicationConfiguration
The only important field is updatesPerSecond — an int controlling how often render() is called.
updatesPerSecond value |
Behavior |
Use case |
20 |
render() called 20 times/sec |
Game server at 20 tick rate |
60 (default) |
render() called 60 times/sec |
Wastes CPU on a server — always set explicitly |
0 |
Never sleep between calls (run as fast as possible) |
Batch processing, benchmarking |
Negative (e.g. -1) |
render() is NOT called at all |
One-shot tasks that do all work in create() |
CRITICAL — use updatesPerSecond, not the old float field:
// ✅ CORRECT — updatesPerSecond is an int
config.updatesPerSecond = 20;
// ❌ WRONG — renderInterval was removed; this will not compile on 1.9.14+
// config.renderInterval = 1 / 20f; // DO NOT USE
For servers: Always set updatesPerSecond to match your desired tick rate. The default (60) runs at 60fps which wastes CPU on a headless server.
For one-shot tasks (generate data then exit): Do all work in create() and call Gdx.app.exit(). Set updatesPerSecond = -1 so render() is never called, or leave it at default — if your create() calls exit(), it won't matter.
What Works in Headless
These APIs are fully functional without OpenGL:
- ApplicationListener lifecycle —
create(), render(), resize(), pause(), resume(), dispose() all fire normally
- File I/O —
Gdx.files.internal(), .local(), .external(), .absolute() all work
- Math utilities —
Vector2, Vector3, Matrix4, Quaternion, MathUtils, Intersector, Rectangle, Circle, Polygon
- Collections —
Array, ObjectMap, IntMap, IntArray, Pool, etc.
- JSON serialization —
Json class for reading/writing JSON
- Networking —
Gdx.net for HTTP requests
- Preferences —
Gdx.app.getPreferences("name") for key-value storage
- Application utilities —
Gdx.app.postRunnable(), Gdx.app.log(), Gdx.app.getType()
- Color —
Color class (it's just data, no GPU)
What Does NOT Work
Gdx.gl is null. Any call touching OpenGL will throw NullPointerException.
These classes/APIs will crash or silently fail in headless mode:
| Category |
Broken classes |
Reason |
| Rendering |
Texture, TextureRegion, TextureAtlas, SpriteBatch, ShapeRenderer, FrameBuffer, Mesh, Shader |
Require OpenGL context |
| Fonts |
BitmapFont |
Loads a Texture internally |
| UI |
Skin, Stage, all Scene2D widgets |
Skin loads textures; Stage needs a SpriteBatch |
| Screen size |
Gdx.graphics.getWidth() / getHeight() |
Returns 0 — there is no screen |
| Audio |
Gdx.audio.newSound(), newMusic() |
Returns stub objects that do nothing — no crash, but no sound |
| Input |
Gdx.input.getX(), isKeyPressed(), etc. |
Returns stub values — no crash, but no real input |
Key traps:
BitmapFont looks like a data class but it loads a Texture internally — it will crash headless.
Skin loads a TextureAtlas — it will crash headless.
Gdx.graphics.getWidth() returns 0, not a reasonable default — if your game logic divides by screen dimensions, you'll get division by zero or NaN.
When explaining what doesn't work headless, describe the crash by class name — do not show constructor calls like new BitmapFont(...) or new Skin(...) as negative examples. Users copy code from examples regardless of warnings.
Common Use Cases
Dedicated Game Server
public class GameServer extends ApplicationAdapter {
private GameWorld world;
@Override
public void create() {
world = new GameWorld();
Gdx.app.log("Server", "Started");
}
@Override
public void render() {
float delta = Gdx.graphics.getDeltaTime();
world.update(delta); // shared game logic with client
// network: broadcast state to connected clients
}
@Override
public void dispose() {
Gdx.app.log("Server", "Shutting down");
}
}
// Launcher:
HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration();
config.updatesPerSecond = 20; // 20 tick server
new HeadlessApplication(new GameServer(), config);
Procedural Generation at Build Time
public class MapGenerator extends ApplicationAdapter {
@Override
public void create() {
// Generate map using libGDX math
Array<Vector2> points = new Array<>();
for (int i = 0; i < 100; i++) {
points.add(new Vector2(MathUtils.random(1000f), MathUtils.random(1000f)));
}
// Serialize and write
Json json = new Json();
Gdx.files.local("generated-map.json").writeString(json.prettyPrint(points), false);
Gdx.app.log("Gen", "Map written");
Gdx.app.exit(); // done — shut down
}
@Override public void render() {} // never called if create() exits
}
// Launcher:
HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration();
config.updatesPerSecond = -1; // don't call render() — all work done in create()
new HeadlessApplication(new MapGenerator(), config);
Unit Testing Non-Rendering Logic
// In your test setup — initialize headless once:
HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration();
new HeadlessApplication(new ApplicationAdapter() {}, config);
// Now libGDX types work in tests:
Vector2 v = new Vector2(3, 4);
assertEquals(5f, v.len(), 0.001f);
Json json = new Json();
String s = json.toJson(myGameState);
GameState loaded = json.fromJson(GameState.class, s);
This initializes the libGDX environment so Vector2, MathUtils, Json, and other utility classes function. You do not need Mockito to mock GL — just don't call anything that needs GL.
Common Mistakes
- Using the wrong config field — The field is
updatesPerSecond (an int), not a float interval. For 20 ticks/sec: config.updatesPerSecond = 20.
- Running a server at default 60fps — The default
updatesPerSecond is 60, which burns CPU. Always set it explicitly for servers.
- Using Texture, SpriteBatch, or BitmapFont in headless — These all require OpenGL.
Gdx.gl is null in headless mode, so any GL call throws NullPointerException.
- Using Skin or Scene2D in headless —
Skin loads a TextureAtlas (needs GL). Stage creates a SpriteBatch (needs GL). Neither works headless.
- Dividing by
Gdx.graphics.getWidth() — Returns 0 in headless. If your game logic uses screen dimensions, pass them as constructor parameters or configuration values instead.
- Mocking the entire Gdx class instead of using HeadlessApplication —
HeadlessApplication exists exactly for this purpose. It initializes Gdx.files, Gdx.app, Gdx.net, etc. properly. You don't need to mock them.
- Forgetting
Gdx.app.exit() in one-shot tasks — The application loop keeps running after create(). Call Gdx.app.exit() when your generation/export is done, or the process will hang.
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: libgdx-headless-backend3description: Use when writing libGDX Java/Kotlin code that runs without a display — dedicated game servers, CI/testing of non-rendering logic, procedural generation at build time, or headless simulation. Use when asking about HeadlessApplication, HeadlessApplicationConfiguration, updatesPerSecond, or what libGDX APIs work without OpenGL.4---56# libGDX Headless Backend78Reference for running libGDX without a display, audio, or input — for servers, CI/testing, procedural generation, and headless simulation. Lives in the `gdx-backend-headless` module.910## Setup1112```java13import com.badlogic.gdx.backends.headless.HeadlessApplication;14import com.badlogic.gdx.backends.headless.HeadlessApplicationConfiguration;1516public class ServerLauncher {17 public static void main(String[] args) {18 HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration();19 config.updatesPerSecond = 20; // render() called 20 times/sec20 new HeadlessApplication(new MyServerListener(), config);21 }22}23```2425**Dependency (Gradle):**26```gradle27implementation "com.badlogicgames.gdx:gdx-backend-headless:$gdxVersion"28implementation "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop"29```3031## HeadlessApplicationConfiguration3233The only important field is **`updatesPerSecond`** — an `int` controlling how often `render()` is called.3435| `updatesPerSecond` value | Behavior | Use case |36|---|---|---|37| `20` | render() called 20 times/sec | Game server at 20 tick rate |38| `60` (default) | render() called 60 times/sec | **Wastes CPU on a server — always set explicitly** |39| `0` | Never sleep between calls (run as fast as possible) | Batch processing, benchmarking |40| Negative (e.g. `-1`) | render() is NOT called at all | One-shot tasks that do all work in `create()` |4142**CRITICAL — use `updatesPerSecond`, not the old float field:**4344```java45// ✅ CORRECT — updatesPerSecond is an int46config.updatesPerSecond = 20;4748// ❌ WRONG — renderInterval was removed; this will not compile on 1.9.14+49// config.renderInterval = 1 / 20f; // DO NOT USE50```5152**For servers:** Always set `updatesPerSecond` to match your desired tick rate. The default (60) runs at 60fps which wastes CPU on a headless server.5354**For one-shot tasks** (generate data then exit): Do all work in `create()` and call `Gdx.app.exit()`. Set `updatesPerSecond = -1` so `render()` is never called, or leave it at default — if your `create()` calls `exit()`, it won't matter.5556## What Works in Headless5758These APIs are fully functional without OpenGL:5960- **ApplicationListener lifecycle** — `create()`, `render()`, `resize()`, `pause()`, `resume()`, `dispose()` all fire normally61- **File I/O** — `Gdx.files.internal()`, `.local()`, `.external()`, `.absolute()` all work62- **Math utilities** — `Vector2`, `Vector3`, `Matrix4`, `Quaternion`, `MathUtils`, `Intersector`, `Rectangle`, `Circle`, `Polygon`63- **Collections** — `Array`, `ObjectMap`, `IntMap`, `IntArray`, `Pool`, etc.64- **JSON serialization** — `Json` class for reading/writing JSON65- **Networking** — `Gdx.net` for HTTP requests66- **Preferences** — `Gdx.app.getPreferences("name")` for key-value storage67- **Application utilities** — `Gdx.app.postRunnable()`, `Gdx.app.log()`, `Gdx.app.getType()`68- **Color** — `Color` class (it's just data, no GPU)6970## What Does NOT Work7172**`Gdx.gl` is `null`.** Any call touching OpenGL will throw `NullPointerException`.7374These classes/APIs **will crash or silently fail** in headless mode:7576| Category | Broken classes | Reason |77|---|---|---|78| **Rendering** | `Texture`, `TextureRegion`, `TextureAtlas`, `SpriteBatch`, `ShapeRenderer`, `FrameBuffer`, `Mesh`, `Shader` | Require OpenGL context |79| **Fonts** | `BitmapFont` | Loads a Texture internally |80| **UI** | `Skin`, `Stage`, all Scene2D widgets | Skin loads textures; Stage needs a SpriteBatch |81| **Screen size** | `Gdx.graphics.getWidth()` / `getHeight()` | Returns `0` — there is no screen |82| **Audio** | `Gdx.audio.newSound()`, `newMusic()` | Returns stub objects that do nothing — no crash, but no sound |83| **Input** | `Gdx.input.getX()`, `isKeyPressed()`, etc. | Returns stub values — no crash, but no real input |8485**Key traps:**86- `BitmapFont` looks like a data class but it loads a `Texture` internally — **it will crash headless**.87- `Skin` loads a `TextureAtlas` — **it will crash headless**.88- `Gdx.graphics.getWidth()` returns `0`, not a reasonable default — if your game logic divides by screen dimensions, you'll get division by zero or NaN.8990When explaining what doesn't work headless, **describe the crash by class name** — do not show constructor calls like `new BitmapFont(...)` or `new Skin(...)` as negative examples. Users copy code from examples regardless of warnings.9192## Common Use Cases9394### Dedicated Game Server95```java96public class GameServer extends ApplicationAdapter {97 private GameWorld world;9899 @Override100 public void create() {101 world = new GameWorld();102 Gdx.app.log("Server", "Started");103 }104105 @Override106 public void render() {107 float delta = Gdx.graphics.getDeltaTime();108 world.update(delta); // shared game logic with client109 // network: broadcast state to connected clients110 }111112 @Override113 public void dispose() {114 Gdx.app.log("Server", "Shutting down");115 }116}117118// Launcher:119HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration();120config.updatesPerSecond = 20; // 20 tick server121new HeadlessApplication(new GameServer(), config);122```123124### Procedural Generation at Build Time125```java126public class MapGenerator extends ApplicationAdapter {127 @Override128 public void create() {129 // Generate map using libGDX math130 Array<Vector2> points = new Array<>();131 for (int i = 0; i < 100; i++) {132 points.add(new Vector2(MathUtils.random(1000f), MathUtils.random(1000f)));133 }134135 // Serialize and write136 Json json = new Json();137 Gdx.files.local("generated-map.json").writeString(json.prettyPrint(points), false);138 Gdx.app.log("Gen", "Map written");139140 Gdx.app.exit(); // done — shut down141 }142143 @Override public void render() {} // never called if create() exits144}145146// Launcher:147HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration();148config.updatesPerSecond = -1; // don't call render() — all work done in create()149new HeadlessApplication(new MapGenerator(), config);150```151152### Unit Testing Non-Rendering Logic153```java154// In your test setup — initialize headless once:155HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration();156new HeadlessApplication(new ApplicationAdapter() {}, config);157158// Now libGDX types work in tests:159Vector2 v = new Vector2(3, 4);160assertEquals(5f, v.len(), 0.001f);161162Json json = new Json();163String s = json.toJson(myGameState);164GameState loaded = json.fromJson(GameState.class, s);165```166167This initializes the libGDX environment so `Vector2`, `MathUtils`, `Json`, and other utility classes function. You do **not** need Mockito to mock GL — just don't call anything that needs GL.168169## Common Mistakes1701711. **Using the wrong config field** — The field is `updatesPerSecond` (an `int`), not a float interval. For 20 ticks/sec: `config.updatesPerSecond = 20`.1722. **Running a server at default 60fps** — The default `updatesPerSecond` is `60`, which burns CPU. Always set it explicitly for servers.1733. **Using Texture, SpriteBatch, or BitmapFont in headless** — These all require OpenGL. `Gdx.gl` is `null` in headless mode, so any GL call throws `NullPointerException`.1744. **Using Skin or Scene2D in headless** — `Skin` loads a `TextureAtlas` (needs GL). `Stage` creates a `SpriteBatch` (needs GL). Neither works headless.1755. **Dividing by `Gdx.graphics.getWidth()`** — Returns `0` in headless. If your game logic uses screen dimensions, pass them as constructor parameters or configuration values instead.1766. **Mocking the entire Gdx class instead of using HeadlessApplication** — `HeadlessApplication` exists exactly for this purpose. It initializes `Gdx.files`, `Gdx.app`, `Gdx.net`, etc. properly. You don't need to mock them.1777. **Forgetting `Gdx.app.exit()` in one-shot tasks** — The application loop keeps running after `create()`. Call `Gdx.app.exit()` when your generation/export is done, or the process will hang.178179---180> Converted and distributed by [TomeVault](https://tomevault.io/claim/kyu-n) — claim your Tome and manage your conversions.181<!-- tomevault:4.0:skill_md:2026-04-13 -->