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
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.
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.