Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ jobs:
command: |
set -e
(cd examples && mm.py ./src/main/java/io/dapr/examples/crypto/README.md)
(cd examples && mm.py ./src/main/java/io/dapr/examples/binarystore/README.md)
(cd examples && mm.py ./src/main/java/io/dapr/examples/workflows/README.md)
(cd spring-boot-examples/pubsub && mm.py README.md)
(cd spring-boot-examples/workflows/patterns && mm.py README.md)
Expand Down
7 changes: 7 additions & 0 deletions examples/components/binarystore/inmemory.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: binarystore
spec:
type: binarystore.in-memory
version: v1
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright 2026 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/

package io.dapr.examples.binarystore;

import io.dapr.client.DaprClientBuilder;
import io.dapr.client.DaprPreviewClient;
import io.dapr.client.domain.SetBinaryFileRequestAlpha1;
import io.dapr.config.Properties;
import io.dapr.config.Property;
import reactor.core.publisher.Flux;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
import java.util.Random;

/**
* Stores, reads, replaces, and deletes files with the Dapr binary store API (alpha).
*
* <p>Run with the in-memory binary store component in components/binarystore.
*/
public class BinaryStoreExample {

private static final String STORE_NAME = "binarystore";
private static final String FILE_NAME = "hello.txt";
private static final String LARGE_FILE_NAME = "large.bin";
private static final int LARGE_FILE_SIZE = 1024 * 1024;
private static final int LARGE_FILE_CHUNKS = 4;

/**
* The main method of the example.
*
* @param args Command line arguments (unused).
* @throws Exception if the client fails to close.
*/
public static void main(String[] args) throws Exception {
Map<Property<?>, String> overrides = Map.of(
Properties.HTTP_PORT, "3500",
Properties.GRPC_PORT, "50001"
);

try (DaprPreviewClient client = new DaprClientBuilder().withPropertyOverrides(overrides).buildPreviewClient()) {
System.out.println("=== Dapr Binary Store Example ===");

// Store a small file from a byte array. overwrite=false fails if the file exists.
byte[] content = "Hello from the Dapr binary store!".getBytes(StandardCharsets.UTF_8);
client.setBinaryFile(STORE_NAME, FILE_NAME, content, false).block();
System.out.println("Stored file: " + FILE_NAME);

byte[] stored = client.getBinaryFileBytes(STORE_NAME, FILE_NAME).block();
System.out.println("Read file content: " + new String(stored, StandardCharsets.UTF_8));

// Replace the file. overwrite=true replaces an existing file.
byte[] updated = "Updated content".getBytes(StandardCharsets.UTF_8);
client.setBinaryFile(STORE_NAME, FILE_NAME, updated, true).block();
byte[] replaced = client.getBinaryFileBytes(STORE_NAME, FILE_NAME).block();
System.out.println("Replaced file content: " + new String(replaced, StandardCharsets.UTF_8));

// Stream a large file in chunks, then read it back as a stream of chunks.
byte[] large = new byte[LARGE_FILE_SIZE];
new Random().nextBytes(large);
int chunkSize = LARGE_FILE_SIZE / LARGE_FILE_CHUNKS;
Flux<byte[]> chunks = Flux.range(0, LARGE_FILE_CHUNKS)
.map(i -> Arrays.copyOfRange(large, i * chunkSize, (i + 1) * chunkSize));
client.setBinaryFile(new SetBinaryFileRequestAlpha1(STORE_NAME, LARGE_FILE_NAME, chunks)
.setOverwrite(true)).block();
long size = client.getBinaryFile(STORE_NAME, LARGE_FILE_NAME)
.map(chunk -> (long) chunk.length)
.reduce(0L, Long::sum)
.block();
System.out.println("Read large file: " + size + " bytes");

client.deleteBinaryFile(STORE_NAME, FILE_NAME).block();
client.deleteBinaryFile(STORE_NAME, LARGE_FILE_NAME).block();
System.out.println("Deleted files");

System.out.println("SUCCESS: The binary store example finished.");
}
}
}
134 changes: 134 additions & 0 deletions examples/src/main/java/io/dapr/examples/binarystore/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
## Dapr Binary Store API Example (alpha)

This example shows how to store, read, replace, and delete files with the Dapr binary store API. The binary store API is an alpha API in Dapr 1.19. Its names and behavior can change in a later release.

### Using the Binary Store API

The Java SDK has these methods on `DaprPreviewClient`:
* `client.setBinaryFile(...)` stores a file from a byte array or from a `Flux<byte[]>` stream. Set `overwrite` to `true` to replace an existing file.
* `client.getBinaryFile(...)` reads a file as a `Flux<byte[]>` of chunks.
* `client.getBinaryFileBytes(...)` reads a whole file into a byte array.
* `client.deleteBinaryFile(...)` deletes a file.

## Pre-requisites

* [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) and Dapr runtime 1.19 or later.
* Java JDK 17 (or greater):
* [Microsoft JDK 17](https://docs.microsoft.com/en-us/java/openjdk/download#openjdk-17)
* [Oracle JDK 17](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK17)
* [OpenJDK 17](https://jdk.java.net/17/)
* [Apache Maven](https://maven.apache.org/install.html) version 3.x.

### Checking out the code

Clone this repository:

```sh
git clone https://github.com/dapr/java-sdk.git
cd java-sdk
```

Then build the Maven project:

```sh
# make sure you are in the `java-sdk` directory.
mvn install
```

Then get into the examples directory:

```sh
cd examples
```

### Initialize Dapr

Run `dapr init` to initialize Dapr in Self-Hosted Mode if it's not already initialized.

### The binary store component

The example uses the in-memory binary store component in `./components/binarystore/inmemory.yaml`:

```yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: binarystore
spec:
type: binarystore.in-memory
version: v1
```

The in-memory store keeps files only in the sidecar's memory. Use a different binary store component, such as `binarystore.aws.s3` or `binarystore.azure.blobstorage`, to keep files.

### Running the Example

`BinaryStoreExample.java` stores a small file, reads it, replaces it, streams a 1 MiB file in chunks, reads it back, and deletes both files:

```java
try (DaprPreviewClient client = new DaprClientBuilder().buildPreviewClient()) {
byte[] content = "Hello from the Dapr binary store!".getBytes(StandardCharsets.UTF_8);
client.setBinaryFile("binarystore", "hello.txt", content, false).block();

byte[] stored = client.getBinaryFileBytes("binarystore", "hello.txt").block();

client.setBinaryFile("binarystore", "hello.txt", "Updated content".getBytes(StandardCharsets.UTF_8), true)
.block();

Flux<byte[]> chunks = ...; // the file content as a stream of chunks
client.setBinaryFile(new SetBinaryFileRequestAlpha1("binarystore", "large.bin", chunks).setOverwrite(true))
.block();
Flux<byte[]> received = client.getBinaryFile("binarystore", "large.bin");

client.deleteBinaryFile("binarystore", "hello.txt").block();
}
```

The SDK sends upload content in chunks of at most 32 KiB.

Use the following command to run this example:

<!-- STEP
name: Run Binary Store Example
expected_stdout_lines:
- "Read file content: Hello from the Dapr binary store!"
- "Replaced file content: Updated content"
- "Read large file: 1048576 bytes"
- "Deleted files"
- "SUCCESS: The binary store example finished."
background: true
output_match_mode: substring
sleep: 30
-->

```bash
dapr run --resources-path ./components/binarystore --app-id binarystore-app --dapr-http-port 3500 --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.binarystore.BinaryStoreExample
```

<!-- END_STEP -->

### Sample Output

```
=== Dapr Binary Store Example ===
Stored file: hello.txt
Read file content: Hello from the Dapr binary store!
Replaced file content: Updated content
Read large file: 1048576 bytes
Deleted files
SUCCESS: The binary store example finished.
```

### Cleanup

To stop the app, run (or press CTRL+C):

<!-- STEP
name: Cleanup
-->

```bash
dapr stop --app-id binarystore-app
```

<!-- END_STEP -->
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Copyright 2026 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/

package io.dapr.it.testcontainers.binarystore;

import io.dapr.client.DaprClientBuilder;
import io.dapr.client.DaprPreviewClient;
import io.dapr.client.domain.SetBinaryFileRequestAlpha1;
import io.dapr.config.Properties;
import io.dapr.exceptions.DaprException;
import io.dapr.testcontainers.Component;
import io.dapr.testcontainers.DaprContainer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import reactor.core.publisher.Flux;

import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.UUID;

import static io.dapr.it.testcontainers.ContainerConstants.DAPR_RUNTIME_IMAGE_TAG;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

/**
* Integration tests for the Dapr binary store Alpha1 API.
*/
@Testcontainers
@Tag("testcontainers")
public class DaprPreviewClientBinaryStoreIT {

private static final String STORE_NAME = "binarystore";

@Container
private static final DaprContainer DAPR_CONTAINER = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG)
.withAppName("binarystore-test-app")
.withComponent(new Component(STORE_NAME, "binarystore.in-memory", "v1", Collections.emptyList()));

private static DaprPreviewClient daprPreviewClient;

@BeforeAll
static void setUp() {
daprPreviewClient = new DaprClientBuilder()
.withPropertyOverride(Properties.HTTP_ENDPOINT, DAPR_CONTAINER.getHttpEndpoint())
.withPropertyOverride(Properties.GRPC_ENDPOINT, DAPR_CONTAINER.getGrpcEndpoint())
.buildPreviewClient();
}

@AfterAll
static void tearDown() throws Exception {
if (daprPreviewClient != null) {
daprPreviewClient.close();
}
}

@Test
public void shouldStoreAndReadSmallFile() {
String fileName = uniqueFileName();
byte[] content = "hello binary store".getBytes(StandardCharsets.UTF_8);

daprPreviewClient.setBinaryFile(STORE_NAME, fileName, content, false).block();

assertArrayEquals(content, daprPreviewClient.getBinaryFileBytes(STORE_NAME, fileName).block());
}

@Test
public void shouldStoreAndReadLargeFileAsChunks() {
String fileName = uniqueFileName();
byte[] content = new byte[1024 * 1024];
new Random(42).nextBytes(content);
// Uneven chunks check that the SDK splits and orders them correctly.
Flux<byte[]> chunks = Flux.just(
Arrays.copyOfRange(content, 0, 100_000),
Arrays.copyOfRange(content, 100_000, 700_000),
Arrays.copyOfRange(content, 700_000, content.length));

daprPreviewClient.setBinaryFile(new SetBinaryFileRequestAlpha1(STORE_NAME, fileName, chunks)).block();

List<byte[]> received = daprPreviewClient.getBinaryFile(STORE_NAME, fileName).collectList().block();
ByteArrayOutputStream joined = new ByteArrayOutputStream();
received.forEach(chunk -> joined.write(chunk, 0, chunk.length));
assertArrayEquals(content, joined.toByteArray());
}

@Test
public void shouldFailWhenFileExistsAndOverwriteIsFalse() {
String fileName = uniqueFileName();
daprPreviewClient.setBinaryFile(STORE_NAME, fileName, bytes("first"), false).block();

DaprException error = assertThrows(DaprException.class,
() -> daprPreviewClient.setBinaryFile(STORE_NAME, fileName, bytes("second"), false).block());

assertEquals("ALREADY_EXISTS", error.getErrorCode());
assertArrayEquals(bytes("first"), daprPreviewClient.getBinaryFileBytes(STORE_NAME, fileName).block());
}

@Test
public void shouldReplaceFileWhenOverwriteIsTrue() {
String fileName = uniqueFileName();
daprPreviewClient.setBinaryFile(STORE_NAME, fileName, bytes("first"), false).block();

daprPreviewClient.setBinaryFile(STORE_NAME, fileName, bytes("second"), true).block();

assertArrayEquals(bytes("second"), daprPreviewClient.getBinaryFileBytes(STORE_NAME, fileName).block());
}

@Test
public void shouldFailToReadDeletedFile() {
String fileName = uniqueFileName();
daprPreviewClient.setBinaryFile(STORE_NAME, fileName, bytes("to delete"), false).block();

daprPreviewClient.deleteBinaryFile(STORE_NAME, fileName).block();

DaprException error = assertThrows(DaprException.class,
() -> daprPreviewClient.getBinaryFileBytes(STORE_NAME, fileName).block());
assertEquals("NOT_FOUND", error.getErrorCode());
}

private static String uniqueFileName() {
return "file-" + UUID.randomUUID() + ".bin";
}

private static byte[] bytes(String value) {
return value.getBytes(StandardCharsets.UTF_8);
}
}
Loading
Loading