Main concepts
In order to access the Odilon server from a Java Application you have to include Odilon client JAR in the
classpath. The interaction is managed by an instance of OdilonClient that connects to the server using the
credentials: AccessKey (ie. username) and SecretKey (ie. password)
/* these are the default values for the Server */
String endpoint = "http://localhost";
int port = 9234;
String accessKey = "odilon";
String secretKey = "odilon";
/** OdilonClient is the interface, ODClient is the implementation */
OdilonClient client = new ODClient(endpoint, port, accessKey, secretKey);
/** ping checks the status of server, it returns the String "ok" when the server is normal */
String ping = client.ping();
if (!ping.equals("ok")) {
System.out.println("ping error -> " + ping);
System.exit(1);
}
The client can also be created using the builder pattern, which sets the connection parameters with a fluent API:
/** ODClient.builder() returns a Builder to construct an ODClient instance using a fluent API */
OdilonClient client = ODClient.builder()
.endpoint("http://localhost")
.port(9234)
.accessKey("odilon")
.secretKey("odilon")
.build();
Odilon stores objects using a flat structure of containers called Buckets. A bucket is like a folder, it
just contains binary objects, potentially a very large number. Every object contained by a bucket has a unique ObjectName
in that bucket; therefore, the pair BucketName + ObjectName is a Unique ID for each object in Odilon.
try {
String bucketName = "bucket-demo";
/** check if the bucket exists, if not create it */
if (client.existsBucket(bucketName))
System.out.println("bucket already exists ->" + bucketName );
else
client.createBucket(bucketName);
} catch (ODClientException e) {
System.out.println(String.valueOf(e.getHttpStatus())+ " " + e.getMessage()+" " + String.valueOf(e.getErrorCode()));
}
Uploading a File requires the Bucket to exist and the ObjectName to be unique for that bucket.
File file = new File("test.pdf");
String bucketName = "bucket-demo";
String objectName = file.getName();
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
client.putObjectStream(bucketName, objectName, inputStream, Optional.of(file.getName()), Optional.empty());
} catch (ODClientException e) {
System.out.println(String.valueOf(e.getHttpStatus())+" " + e.getMessage()+" " + String.valueOf(e.getErrorCode()));
} catch (FileNotFoundException | IOException e1) {
System.out.println(e1.getClass().getName() + " " + e1.getMessage());
}
Listing bucket's objects
In addition to the binary file, an Object has Metadata (called ObjectMetadata) that is returned by some of
the API calls. Odilon allows to retrieve Objects individually by BucketName + ObjectName and also supports to list
the contents of a bucket and other simple queries.
try {
/** list all bucket's objects */
ResultSet<Item <ObjectMetadata>> resultSet = client.listObjects(bucket.getName());
while (resultSet.hasNext()) {
Item item = resultSet.next();
if (item.isOk())
System.out.println("ObjectName:"+item.getObject().objectName+" | file: " + item.getObject().fileName);
else
System.out.println(item.getErrorString());
}
} catch (ODClientException e) {
System.out.println(String.valueOf( e.getHttpStatus())+ " "+e.getMessage() + " "+String.valueOf(e.getErrorCode()));
}
Searching objects with Lucene queries
Besides listObjects, which returns a paginated ResultSet, the client can query the server's embedded Apache
Lucene index with search(SearchQuery). A search returns a List<SearchResult> with up to 1000
items per call (use offset and maxResults to page through larger results). Search must be enabled on the
server (search.enabled=true). See Queries for details and REST examples.
try {
/** free text over object name, file name, tags and metadata values */
SearchQuery q1 = new SearchQuery().q("invoice").maxResults(50);
for (SearchResult r : client.search(q1))
System.out.println(r.bucketName + "/" + r.objectName + " | " + r.fileName + " | score " + r.score);
/** file name wildcard + bucket + metadata + date range */
SearchQuery q2 = new SearchQuery()
.bucketName("docs")
.fileName("tolomei*")
.meta("department", "legal")
.lastModifiedFrom(OffsetDateTime.parse("2026-01-01T00:00:00Z"))
.lastModifiedTo(OffsetDateTime.parse("2026-09-30T23:59:59Z"))
.offset(0)
.maxResults(100);
List<SearchResult> results = client.search(q2);
results.forEach(r -> System.out.println(r));
/** index status and rebuild */
IndexStatus status = client.searchStatus();
boolean started = client.searchRebuild();
} catch (ODClientException e) {
System.out.println(String.valueOf(e.getHttpStatus()) + " " + e.getMessage() + " " + String.valueOf(e.getErrorCode()));
}
Sample programs
Create Bucket
SampleBucketCreation.java
List Buckets
SampleListBuckets.java
Upload file
Sample putObject and download ObjectMetadata
SamplePutObject.java
List all objects in a Bucket
SampleListObjects.java
Get presigned urls of Objects
A presigned URL is a method to grant temporary access to an Object, for example in an HTML webpage. It remains
valid for a limited period of time which is specified when the URL is generated. This example shows presigned urls
that last 7 days and 5 minutes.
SamplePresignedUrl.java
This unit test example downloads presigned urls from a
Bucket.
TestPresignedUrl.java
Minio to Odilon migration
This class shows how to migrate from Minio Object Storage to Odilon (it uses Minio SDK 6.x). It is a multithreaded
gateway that iterates reading a buffer of items from Minio and sending them in parallel to Odilon.
https://github.com/atolomei/miniomigration
Dellemuse - Object Storage Service
Dellemuse is a free service that enriches the cultural experience in museums and historical buildings through AI-generated audio guides that visitors can listen to on
their mobile phones.
It uses Odilon to store images, audio and other files. The server is a Spring Boot Java application that uses PostgreSQL and Odilon for data storage. For Odilon, it has a thin service layer
called ObjectStorageService
Dellemuse ObjectStorageService