TagLib Java1.3.0GitHub ↗

Resource ownership and explicit saving

Files, streams, and failures

Use an AudioFile session when several operations need the same open file. Its properties and pictures can be read as snapshots, while tag() and file() expose live objects. Close the session with try-with-resources.

Stage changes and save explicitly

Java
try (var file = new AudioFile(Path.of("song.flac"), ReadOptions.TAGS_ONLY)) {
    var replacement = file.properties().toBuilder()
        .put(PropertyKey.TITLE, "New title")
        .build();
    var result = file.replaceProperties(replacement);
    if (!result.rejected().isEmpty()) {
        throw new IllegalArgumentException("Unrepresentable values: "
            + result.rejected().asMap());
    }
    file.save();
}

replaceProperties stages changes and returns accepted and rejected values. It does not save. Closing a session does not save either. Operations documented as immediate writes, such as particular native strip operations, are exceptions to the usual staging model; read that operation’s contract before calling it.

A session and its live tags are single-threaded. Do not share one open file object between concurrent edits. Fluent path edits serialize writes through that API to the same resolved path, but that does not coordinate other programs or every lower-level writer.

Borrowed streams

The stream overloads accept cz.skyba.taglib.toolkit.IOStream, which supports seeking. A regular java.io.InputStream cannot provide the random access required by these readers and writers.

Java
try (var stream = new cz.skyba.taglib.toolkit.FileStream(Path.of("song.flac"))) {
    var metadata = TagLib.read(stream);
    var replacement = metadata.properties().toBuilder()
        .put(PropertyKey.TITLE, "New title")
        .build();
    var result = TagLib.write(stream, replacement);
    if (!result.rejected().isEmpty()) {
        System.err.println(result.rejected().asMap());
    }
}

High-level reads and writes start at byte zero and do not restore the previous cursor. You can read and then write the same stream without rewinding it manually. The calls leave the stream open; the surrounding try-with-resources in this example closes it.

Construction Who closes storage?
TagLib.read(Path) / path writes The call opens and closes its handle.
AudioFile(Path) or a native path constructor The file session owns the handle; close the session.
Read, write, or file constructor taking IOStream The caller owns the stream; close it after the file session.

Reads and cursor operations in a live file share the underlying cursor. Do not run unrelated reads on the same borrowed stream while its session is active. Borrowing a stream does not make it immutable, rewind it after each operation, or transfer its lifetime to the library.

Failures and partial writes

Failure Meaning and handling
UncheckedIOException Storage access or decoding failed. The underlying I/O cause is available; report the file path with the error.
InvalidAudioFileException The input is invalid or not a readable supported format. Do not treat this as an empty tag.
MetadataEditException A fluent edit has text values or picture roles that cannot be represented. Those rejections prevent saving.
UnsupportedOperationException The requested feature is unavailable, such as artwork editing in a tracker, or snapshot reading through a custom resolver without snapshot support.
TagLibException A format-level open/save operation was rejected, for example a save to read-only storage.
IllegalStateException The operation uses a closed stream or session.

Bad arguments can also raise IllegalArgumentException or NullPointerException. These usually indicate an invalid request rather than a bad audio file. A complete-map write reports representability failures in its result and may still save accepted values; a fluent edit rejects them before saving.

None of these APIs turns an in-place save into a transaction. Once a write starts, a storage failure may leave the file partially changed. If preserving the original is a requirement, edit a separate copy and replace the original only after the operation succeeds. This is an application policy, not something save() does automatically.

Relationship to C++ TagLib

This is an independent Java adaptation, not an official TagLib release or a one-to-one wrapper. The parity target is TagLib 2.3.2. The upstream manual, property mappings, and FileRef documentation explain the design behind the native interfaces. Use this guide and the Java source for Java method names, exceptions, ownership rules, and read-style behavior.

Source and issues are on GitHub. Benchmark methodology and results are in BENCHMARK.md. The project is distributed under LGPL 2.1 or MPL 2.0; the original TagLib project and its contributors are credited in the repository.