Obsidian provides a unified storage facade built around named disks. Each disk is an isolated backend — local filesystem, S3, or any custom implementation. The active disk is resolved at runtime from environment variables; all operations are available directly on the StorageManager or on a specific disk via disk().
# Root directory on the filesystem
STORAGE_LOCAL_ROOT=storage/app
# Base URL used to generate public file URLs
STORAGE_LOCAL_URL=https://example.com/storage
# Default disk (registered disks: local, public)
STORAGE_DISK=local
| Disk | Path | Use |
|---|---|---|
| local | STORAGE_LOCAL_ROOT | Private files |
| public | STORAGE_LOCAL_ROOT/public | Publicly accessible files |
@Inject
private StorageManager storage;
storage.put("reports/q3.pdf", bytes); // Write bytes
storage.put("exports/users.csv", inputStream); // Write a stream
byte[] content = storage.get("reports/q3.pdf"); // Read as bytes
InputStream stream = storage.stream("reports/q3.pdf"); // Open a stream
storage.exists("reports/q3.pdf"); // Check existence
storage.delete("reports/q3.pdf"); // Delete
List<String> files = storage.list("reports"); // List directory
String url = storage.url("reports/q3.pdf");
// → https://example.com/storage/reports/q3.pdf
// Default disk (configured via STORAGE_DISK)
storage.put("file.txt", bytes);
// Specific disk
storage.disk("public").put("images/banner.png", bytes);
storage.disk("local").delete("tmp/import.csv");
Part part = request.raw().getPart("avatar");
MultipartFile file = new MultipartFile(part);
storage.putFile("avatars", file);
// → avatars/550e8400-e29b-41d4-a716-446655440000.png
file.getOriginalName(); // "photo.png"
file.getContentType(); // "image/png"
file.getSize(); // 204800 (bytes)
file.getExtension(); // "png"
file.getInputStream(); // raw stream
public class S3Disk implements StorageDisk {
@Override
public void put(String path, byte[] content) { ... }
@Override
public void put(String path, InputStream stream) { ... }
@Override
public byte[] get(String path) { ... }
// implement stream(), delete(), exists(), list(), url()
}
// Register at boot
manager.addDisk("s3", new S3Disk(...));
putFile() always generates a UUID filename — the original name is not preserved on diskLocalDisk creates parent directories automatically on every write../) throw a StorageException immediatelyStorageManager is a singleton and injectable via @InjectStorageException — no checked IOExceptions leak out