Uploading & deduplication¶
Not yet on Maven Central
dedup4j 0.1.0 has not been published. These coordinates are the
intended release coordinates and cannot be resolved from Maven Central
today. The Maven Central namespace is confirmed in release gate G0 and
this file is the single place it is edited.
BlobStore is the upload front door. It takes bytes in whatever form your
application already has them and returns a BlobReference.
The facade¶
public interface BlobStore {
BlobReference store(MultipartFile file);
BlobReference store(Path path);
BlobReference store(byte[] content, String filename, String contentType);
BlobReference store(InputStream content, long sizeBytes, String filename,
String contentType, Map<String, String> metadata);
BatchStoreResult storeAll(MultipartFile[] files);
}
Four store overloads for the four shapes bytes usually arrive in — a web
upload, a file on disk, an in-memory array, or a stream you are already
holding.
Uploads are capped at 25 MB by default
dedup4j.deduplication.max-upload-size defaults to 25MB and is
enforced. Larger uploads are rejected. Raise it deliberately — the content
is read into memory to be hashed.
The stream overload needs the size up front
sizeBytes is a parameter because content length is part of content
identity and dedup4j will not buffer an entire stream to discover it.
What store returns¶
public record BlobReference(
UUID assetContentId, // your handle — store this
ContentHash contentHash, // content identity
String contentType,
String storageProvider,
String bucketOrContainer,
String objectKey,
boolean duplicate // were these bytes already here?
) {}
assetContentId is the only field most applications persist. The rest
describe where the bytes landed and are useful for diagnostics and for
building your own download URLs via
location().
Content identity¶
Identity is the triple, not the hash alone. The digest is SHA-256, which is where collision resistance actually comes from; size is an additional discriminator, so two objects match only if both agree.
The algorithm is fixed
SHA-256 is not configurable. Changing it would orphan every previously
stored object, since identity is the hash — see
ContentHasher.
What identity is not:
- not the filename —
invoice.pdfanda-copy.pdfwith the same bytes are the same content - not the content type
- not the metadata
- not the upload time or uploader
Only the bytes decide.
Storing the same bytes twice¶
BlobReference first = blobStore.store(bytes, "invoice.pdf", "application/pdf");
BlobReference second = blobStore.store(bytes, "copy.pdf", "application/pdf");
first.duplicate(); // false — new content
second.duplicate(); // true — recognised
second.assetContentId().equals(first.assetContentId()); // true
On the second call dedup4j hashes the content, finds an existing row, uploads nothing, and returns a reference to the object already stored.
A duplicate store already counts for you
store returning duplicate: true has already incremented the
reference count. New content is created at a count of one; a duplicate
store retains the existing content.
So the rule is symmetric: every store call yields exactly one
reference, and needs exactly one matching
release. Do not call retain after
a duplicate store — that would count the same record twice, and the
content would never be deleted.
retain is for the other case: a new record pointing at content you did not
just store, such as copying an existing attachment onto a second document
without re-uploading bytes.
Batch uploads¶
BatchStoreResult result = blobStore.storeAll(files);
result.allSucceeded();
result.successes(); // List<BlobStoreSuccess>
result.failures(); // List<BlobStoreFailure>
Partial success is the normal case¶
storeAll is not all-or-nothing. Each file succeeds or fails
independently, and one bad file does not discard the rest.
The outcome type is a sealed interface, so the compiler can check you handled both:
public sealed interface BatchStoreOutcome
permits BlobStoreSuccess, BlobStoreFailure {
int index();
String filename();
}
for (BatchStoreOutcome outcome : result.outcomes()) {
switch (outcome) {
case BlobStoreSuccess s ->
attach(s.reference().assetContentId());
case BlobStoreFailure f ->
report(f.index(), f.filename(), f.failure());
}
}
index() is the file's position in the array you passed, so a failure maps
back to the exact input — necessary when several uploads share a filename or
when the filename is absent.
Check failures()
Treating a BatchStoreResult as success because the call returned is the
easiest mistake to make here. The call returning means the batch ran, not
that every file stored. Use allSucceeded() before assuming.
What dedup4j does not do on upload¶
- No virus scanning or content inspection. Bytes are stored as given.
- No image processing, transcoding, or thumbnails.
- No access control. Anyone who can call
storecan store. - No atomicity with your own writes across the database and the object store — see Architecture & limitations.
Next¶
- Retrieval, retain & release — reading back and reference counting
- Public API — the full surface
- Troubleshooting — when a store fails