Skip to content

Public API

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.

The types dedup4j expects you to use. Everything here is in com.edem.dedup4j.*.

Records, not beans

Every model type is an immutable record with validation in its compact constructor. There are no setters, and an invalid instance cannot be constructed.

Entry points

BlobStore

The upload facade. Auto-configured by the starter.

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);
}

See Uploading & deduplication.

BlobDeduplicationService

Retrieval and reference counting. Auto-configured.

public interface BlobDeduplicationService {
    BlobReference store(StoreBlobCommand command);
    void retain(UUID assetContentId);
    void release(UUID assetContentId);
    BlobResource get(UUID assetContentId);
    BlobLocation location(UUID assetContentId);
}

Its store takes a StoreBlobCommand directly — the lower-level entry point BlobStore delegates to. See Retrieval, retain & release.

Models

BlobReference

Returned by every store.

public record BlobReference(
    UUID assetContentId,
    ContentHash contentHash,
    String contentType,
    String storageProvider,
    String bucketOrContainer,
    String objectKey,
    boolean duplicate
) {
    BlobLocation location();
}

assetContentId is the handle to persist. duplicate reports whether the bytes already existed.

ContentHash

public record ContentHash(String algorithm, String hash, long sizeBytes) {}

Content identity is the whole triple. The algorithm is SHA-256.

BlobResource

Returned by get. Holds an open stream and implements AutoCloseable.

public record BlobResource(
    String objectKey,
    InputStream content,
    long sizeBytes,
    String contentType,
    Map<String, String> metadata
) implements AutoCloseable {}
try (BlobResource resource = blobs.get(id)) { ... }

BlobLocation

public record BlobLocation(
    String provider,
    String bucketOrContainer,
    String objectKey
) {}

Note the component is provider, while BlobReference calls the same concept storageProvider.

Everything needed to build a presigned URL with your provider's own SDK — see why dedup4j does not issue them.

StoreBlobCommand

public record StoreBlobCommand(
    InputStream content,
    String filename,
    String contentType,
    long sizeBytes,
    Map<String, String> metadata
) {}

Batch results

public sealed interface BatchStoreOutcome
        permits BlobStoreSuccess, BlobStoreFailure {
    int index();
    String filename();
}

public record BlobStoreSuccess(int index, String filename, BlobReference reference)
        implements BatchStoreOutcome {}

public record BlobStoreFailure(int index, String filename, RuntimeException failure)
        implements BatchStoreOutcome {}

public record BatchStoreResult(List<BatchStoreOutcome> outcomes) {
    List<BlobStoreSuccess> successes();
    List<BlobStoreFailure> failures();
    boolean allSucceeded();
}

Sealed, so a switch over outcomes is exhaustively checked. index() maps back to the position in the array you submitted.

Extension points

Each is an auto-configured bean declared @ConditionalOnMissingBean — declare your own and the default steps aside.

BlobStorage

The storage SPI. Implement it to support a provider dedup4j does not ship.

public interface BlobStorage {
    StoredBlob put(PutBlobRequest request);
    BlobResource get(String objectKey);
    void delete(String objectKey);
    boolean exists(String objectKey);
}

delete must be idempotent — an already-missing object counts as deleted.

public record PutBlobRequest(
    String objectKey, InputStream content, long sizeBytes,
    String contentType, String originalFilename, Map<String, String> metadata
) {}

public record StoredBlob(
    String objectKey, String provider, String bucketOrContainer,
    long sizeBytes, String contentType, String checksum, Instant createdAt
) {}

See custom providers.

ObjectKeyStrategy

Decides where content lands in the store.

public interface ObjectKeyStrategy {
    String generateKey(ContentHash contentHash);
}

The key is derived from content identity alone — the same bytes always produce the same key, which is what makes deduplication work. A strategy that introduces randomness breaks that.

ContentHasher

public interface ContentHasher {
    ContentHash hash(InputStream inputStream) throws IOException;
}

Changing this orphans existing content

Identity is the hash. Existing rows keep hashes from the old algorithm, so previously stored content will never match again and will be stored a second time.

Reconciliation

Not auto-configured — construct it yourself.

public interface LogicalReferenceCountSource {
    Map<UUID, Long> countLogicalReferences();
}

public record ReconciliationMismatch(
    UUID assetContentId,
    long expectedReferenceCount,
    long actualReferenceCount
) {}

public record ReconciliationReport(
    long checkedContentCount,
    List<ReconciliationMismatch> mismatches
) {}

LogicalReferenceCountSource is a functional interface: you tell dedup4j what the counts should be, because it cannot enumerate your records.

Exceptions

All unchecked. See Troubleshooting.

Dedup4jException
├── BlobValidationException
├── BlobStorageException
├── BlobHashingException
├── ContentNotFoundException
└── ReferenceCountUnderflowException

DuplicateContentIdentityException    ← extends RuntimeException directly