| name | libgdx-file-io-preferences |
| description | Use when writing libGDX Java/Kotlin code involving file I/O (FileHandle, Gdx.files, FileType) or persistent storage (Preferences). Use when debugging files not found, write failures on Android/iOS, wrong file paths across platforms, or preferences not persisting. |
libGDX File I/O & Preferences
Quick reference for FileHandle, Files.FileType, and Preferences. Covers file reading/writing, platform-specific path resolution, and persistent key-value storage.
FileType Enum (Files.FileType)
Five values: Classpath, Internal, External, Absolute, Local.
| FileType | Writable | Description |
|---|
Classpath | No | Java classpath resource. Most restrictive — no list(), no isDirectory(), no Android audio. |
Internal | No | Asset directory. Falls back to classpath on desktop if file not found. |
External | Yes | User/app external storage. |
Local | Yes | App-private storage. |
Absolute | Yes | Literal filesystem path. |
Classpath and Internal are always read-only. Writing throws GdxRuntimeException.
Platform Path Resolution — WHERE Each FileType Points
| FileType | Desktop (LWJGL3) | Android | iOS (RoboVM) | GWT |
|---|
Internal | Working dir, falls back to classpath | APK assets/ via AssetManager | App bundle (NSBundle.mainBundle) | Preloaded assets |
Local | Working dir (new File("").getAbsolutePath()) | Context.getFilesDir() (app private) | $HOME/Library/local/ | Not supported (throws) |
External | User home (System.getProperty("user.home")) | Context.getExternalFilesDir(null) (app-scoped) | $HOME/Documents/ | Not supported (throws) |
Absolute | Literal path | Literal path | Literal path | Not supported (throws) |
Classpath | Classloader resource | Classloader resource | Classloader resource | Preloaded assets |
Gotchas:
- Android
External uses getExternalFilesDir(null) (app-scoped, e.g. /storage/emulated/0/Android/data/<pkg>/files/). This does NOT require WRITE_EXTERNAL_STORAGE permission. Files are deleted on app uninstall. Older libGDX versions used Environment.getExternalStorageDirectory() which DID need the permission — that is no longer the case.
- Desktop
Local and Internal both resolve relative to the JVM working directory. When launching from IntelliJ/Gradle, this may not be the assets/ folder — configure the working directory in your run configuration.
- iOS
Local and External are different paths (Library/local/ vs Documents/), unlike what some documentation claims.
- GWT supports only
Internal and Classpath. All other types throw GdxRuntimeException. GWT files are read-only.
Gdx.files Methods
FileHandle fh = Gdx.files.internal("data/map.tmx");
FileHandle fh = Gdx.files.local("saves/slot1.json");
FileHandle fh = Gdx.files.external("screenshots/s.png");
FileHandle fh = Gdx.files.absolute("/tmp/debug.log");
FileHandle fh = Gdx.files.classpath("com/lib/default.json");
String path = Gdx.files.getExternalStoragePath();
String path = Gdx.files.getLocalStoragePath();
boolean ok = Gdx.files.isExternalStorageAvailable();
boolean ok = Gdx.files.isLocalStorageAvailable();
Gdx.files.classpath() does exist — it is a real method on the Files interface. Classpath is a real FileType enum value. However, prefer Internal for game assets — Classpath cannot be used with Android audio, cannot list directories, and always reports isDirectory() = false.
DO NOT use new FileHandle("path") for cross-platform code — the public constructors create Absolute handles with no backend awareness. Always use Gdx.files.*().
FileHandle API
Reading
InputStream is = fh.read();
BufferedInputStream bis = fh.read(bufferSize);
Reader r = fh.reader();
Reader r = fh.reader("UTF-8");
BufferedReader br = fh.reader(bufferSize);
BufferedReader br = fh.reader(bufferSize, "UTF-8");
String s = fh.readString();
String s = fh.readString("UTF-8");
byte[] b = fh.readBytes();
int n = fh.readBytes(bytes, offset, size);
ByteBuffer bb = fh.map();
ByteBuffer bb = fh.map(FileChannel.MapMode.READ_ONLY);
Gotcha: readString() without a charset uses the platform default charset, NOT UTF-8. On most modern JVMs this is UTF-8, but it's not guaranteed. Always pass "UTF-8" explicitly for cross-platform consistency.
Writing
All write methods require boolean append — there is no overload without it.
OutputStream os = fh.write(false);
OutputStream os = fh.write(true, bufferSize);
fh.write(inputStream, false);
Writer w = fh.writer(false);
Writer w = fh.writer(false, "UTF-8");
fh.writeString("data", false);
fh.writeString("data", true, "UTF-8");
fh.writeBytes(bytes, false);
fh.writeBytes(bytes, offset, length, false);
Writing to Classpath or Internal throws GdxRuntimeException. Parent directories are created automatically.
Metadata
String s = fh.path();
String s = fh.name();
String s = fh.extension();
String s = fh.nameWithoutExtension();
String s = fh.pathWithoutExtension();
FileType t = fh.type();
boolean b = fh.exists();
boolean b = fh.isDirectory();
long len = fh.length();
long ms = fh.lastModified();
File f = fh.file();
WARNING: fh.file() on Android Internal files returns a File with a relative path that does NOT point to an accessible filesystem location. Assets live inside the APK. The returned File will report exists() = false. Use read()/readString()/readBytes() instead. On GWT, file() throws GdxRuntimeException.
Directory Operations
FileHandle[] children = fh.list();
FileHandle[] children = fh.list(".png");
FileHandle[] children = fh.list(fileFilter);
FileHandle[] children = fh.list(filenameFilter);
FileHandle child = fh.child("sub/file.txt");
FileHandle parent = fh.parent();
FileHandle sibling = fh.sibling("other.txt");
list() returns an empty array (not null) when there are no children. Throws GdxRuntimeException for Classpath. On Android, list() for Internal files works via AssetManager.list().
Gotcha: exists() for Internal directories on Android is extremely slow (tries assets.open(), catches exception, then calls assets.list()). The source code contains the comment "This is SUPER slow!" Empty Internal directories on Android report isDirectory() = false.
File Operations
fh.mkdirs();
fh.copyTo(destHandle);
fh.moveTo(destHandle);
boolean ok = fh.delete();
boolean ok = fh.deleteDirectory();
fh.emptyDirectory();
fh.emptyDirectory(true);
mkdirs(), delete(), deleteDirectory(), emptyDirectory() all throw GdxRuntimeException for Classpath/Internal.
Static Utilities
FileHandle tmp = FileHandle.tempFile("prefix");
FileHandle tmpd = FileHandle.tempDirectory("prefix");
These use java.io.File.createTempFile() internally. Not available on GWT.
Preferences
Persistent key-value storage. Backed by platform-native mechanisms.
Getting Preferences
Preferences prefs = Gdx.app.getPreferences("com.mygame.settings");
The name must be valid as a filename (it becomes one on Desktop/iOS). Use reverse-domain naming to avoid collisions — all libGDX apps share the same .prefs/ directory on desktop.
DO NOT call getPreferences() in a constructor or static initializer — Gdx.app is not initialized until create().
Put Methods (all return Preferences for chaining)
prefs.putBoolean("sound", true)
.putInteger("level", 5)
.putLong("score", 999999L)
.putFloat("volume", 0.8f)
.putString("name", "Player1")
.flush();
prefs.put(Map.of("a", true, "b", 42));
There is no putDouble(). Store doubles as strings or two floats.
Get Methods
boolean b = prefs.getBoolean("sound");
boolean b = prefs.getBoolean("sound", true);
int i = prefs.getInteger("level");
int i = prefs.getInteger("level", 1);
long l = prefs.getLong("score");
float f = prefs.getFloat("volume");
String s = prefs.getString("name");
String s = prefs.getString("name", "Unknown");
| No-default call | Returns |
|---|
getBoolean(key) | false |
getInteger(key) | 0 |
getLong(key) | 0 |
getFloat(key) | 0 |
getString(key) | "" (empty string) |
Other Methods
Map<String, ?> all = prefs.get();
boolean exists = prefs.contains("key");
prefs.remove("key");
prefs.clear();
prefs.flush();
There is no save(), commit(), or apply() — it is flush().
flush() — Critical
flush() is the only way to persist changes to disk. Without it, all puts are lost when the app exits. There is no auto-flush on any platform.
prefs.putInteger("score", 100);
prefs.putInteger("score", 100);
prefs.flush();
Preferences Storage Per Platform
| Platform | Storage Mechanism | Location |
|---|
| Desktop (LWJGL3) | Java Properties XML file | ~/.prefs/<name> (configurable via Lwjgl3ApplicationConfiguration.setPreferencesConfig()) |
| Android | SharedPreferences | /data/data/<pkg>/shared_prefs/<name>.xml |
| iOS (RoboVM) | NSMutableDictionary → .plist | ~/Library/<name>.plist |
| GWT | Browser localStorage | Keyed by "<name>:<key><type>" |
| Headless | Java Properties XML file | ~/.prefs/<name> (same as desktop) |
Gotchas:
- Desktop: All libGDX apps share
~/.prefs/ — use unique names.
- iOS: Multiple
getPreferences() calls with the same name return independent instances that do not share in-memory state. Get the instance once and reuse it.
- GWT:
localStorage is typically limited to ~5 MB per origin.
- Preferences are not thread-safe on any platform.
GWT File Restrictions Summary
| Feature | Supported on GWT? |
|---|
Gdx.files.internal() | Yes (preloaded, read-only) |
Gdx.files.classpath() | Yes (preloaded, read-only) |
Gdx.files.local() | No — throws GdxRuntimeException |
Gdx.files.external() | No — throws GdxRuntimeException |
Gdx.files.absolute() | No — throws GdxRuntimeException |
fh.file() | No — throws GdxRuntimeException |
| Any write operation | No — throws GdxRuntimeException |
fh.map() | No — throws GdxRuntimeException |
Preferences | Yes — uses browser localStorage |
On GWT, Preferences is the only way to persist data.
Integration Notes
- AssetManager uses
Internal files by default via InternalFileHandleResolver. Pass LocalFileHandleResolver or ExternalFileHandleResolver to the constructor to change this.
- For platform-specific file behavior details, see the backend skills:
libgdx-android-backend, libgdx-lwjgl3-desktop, libgdx-ios-robovm.
Common Mistakes
- Writing to Internal files —
Gdx.files.internal("save.json").writeString(...) throws GdxRuntimeException. Internal is read-only. Use Gdx.files.local() for writable app-private storage.
- Forgetting
flush() on Preferences — Without flush(), all puts are lost on app exit. There is no auto-flush, no save(), no commit(). The method is flush().
- Using
java.io.File or java.nio.file.Path instead of FileHandle — Breaks on Android (assets are inside the APK) and iOS. Always use Gdx.files.*() for cross-platform code.
- Calling
fh.file() on Android Internal files — Returns a File with a relative path that doesn't exist on the filesystem. Use fh.read() / fh.readString() / fh.readBytes() instead.
- Omitting the
append boolean on write methods — writeString(str, append) requires the boolean. There is no overload without it. Use false to overwrite, true to append.
- Assuming
readString() uses UTF-8 — It uses the platform default charset. Always pass "UTF-8" explicitly: fh.readString("UTF-8").
- Confusing
Local paths across platforms — Desktop Local is the working directory (may not be where you expect in IDE). Android Local is Context.getFilesDir() (app-private). They are not the same concept.
- Calling
Gdx.files.*() or Gdx.app.getPreferences() before create() — Gdx.files and Gdx.app are null until the application is initialized. Access them in create() or later.
- Using
Gdx.files.external() expecting shared storage on modern Android — External maps to getExternalFilesDir(null) (app-scoped, deleted on uninstall). It is NOT shared public storage.
- Using file I/O on GWT — Only
Internal/Classpath reads and Preferences work. All writes, local(), external(), absolute(), and file() throw exceptions.
- Using
new FileHandle("path") in cross-platform code — The public constructors create Absolute handles with no backend awareness. The Javadoc warns: "Do not use this constructor in case you write something cross-platform."
- Not using unique Preferences names — On desktop, all libGDX apps share
~/.prefs/. A generic name like "settings" will collide. Use reverse-domain naming: "com.mygame.settings".