Skip to content

Getting Started

Prerequisites

  • Java 11 or later

Installation

Add JAWS to your Maven project:

<dependency>
    <groupId>org.tomitribe</groupId>
    <artifactId>jaws-s3</artifactId>
    <version>2.1.0</version>
</dependency>

Creating an S3Client

The S3Client is the entry point. It wraps an AWS S3AsyncClient:

import org.tomitribe.jaws.s3.S3Client;
import software.amazon.awssdk.services.s3.S3AsyncClient;

S3AsyncClient asyncClient = S3AsyncClient.builder().build();
S3Client s3 = new S3Client(asyncClient);

Working with Buckets

Get a reference to an existing bucket:

S3Bucket bucket = s3.getBucket("my-bucket");

Or create a new one:

S3Bucket bucket = s3.createBucket("my-new-bucket");

List all buckets:

s3.buckets().forEach(b -> System.out.println(b.getName()));

Defining Your First Proxy

Imagine a bucket with this structure:

my-bucket/
  config.properties
  users/
    alice.json
    bob.json

Define interfaces that mirror this structure:

public interface UserRepository extends S3.Dir {
    @Name("config.properties")
    S3File config();

    Users users();
}

public interface Users extends S3.Dir {
    Stream<UserFile> users();

    UserFile user(String name);
}

@Match(".*\\.json")
public interface UserFile extends S3.File {
    default String getUserName() {
        return file().getName().replaceAll("\\.json$", "");
    }
}

The @Match(".*\\.json") annotation does double duty: listing methods like Stream<UserFile> users() will only return .json files, and single-arg methods like UserFile user(String name) will reject names that don't match the pattern (throwing IllegalArgumentException).

Create and use the proxy:

S3Bucket bucket = s3.getBucket("my-bucket");
UserRepository root = bucket.as(UserRepository.class);

// Read config
String config = root.config().getValueAsString();

// List users
root.users().users().forEach(user ->
    System.out.println(user.getUserName())
);

// Get a specific user — name is validated against @Match
UserFile alice = root.users().user("alice.json");
String json = alice.getValueAsString();

Testing

JAWS ships a jaws-s3-test module with an in-memory S3 backend powered by S3Proxy. Add it as a test dependency alongside JUnit 5:

<dependency>
    <groupId>org.tomitribe</groupId>
    <artifactId>jaws-s3-test</artifactId>
    <version>2.1.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.10.2</version>
    <scope>test</scope>
</dependency>

Register a MockS3Extension in your test. It starts a local S3 server before each test and tears it down after:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.tomitribe.jaws.s3.MockS3Extension;
import org.tomitribe.jaws.s3.Name;
import org.tomitribe.jaws.s3.S3;
import org.tomitribe.jaws.s3.S3Asserts;
import org.tomitribe.jaws.s3.S3Client;
import org.tomitribe.jaws.s3.S3File;

import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class GettingStartedTest {

    @RegisterExtension
    private final MockS3Extension mockS3 = new MockS3Extension();

    @Test
    void test() {
        final S3Client s3Client = new S3Client(mockS3.getS3Client());

        final UserRepository root = s3Client.createBucket("my-bucket")
                .put("config.properties", "color=red")
                .put("users/alice.json", "{\"name\":\"Alice\"}")
                .put("users/bob.json", "{\"name\":\"Bob\"}")
                .as(UserRepository.class);

        // Read entries
        assertEquals("color=red", root.config().getValueAsString());
        assertEquals("{\"name\":\"Alice\"}", root.users().user("alice.json").getValueAsString());

        // List entries
        final String names = root.users().users()
                .map(UserFile::getUserName)
                .sorted()
                .collect(Collectors.joining(", "));

        assertEquals("alice, bob", names);

        // Add entry
        root.users().user("charlie.json").setValueAsString("{\"name\":\"Charlie\"}");

        // Review all results
        S3Asserts.of(mockS3.getS3Client(), "my-bucket")
                .snapshot()
                .assertContent("users/charlie.json", "{\"name\":\"Charlie\"}")
                .assertContent("users/alice.json", "{\"name\":\"Alice\"}")
                .assertExists("config.properties")
                .assertNotExists("users/dave.json");
    }

    public interface UserRepository extends S3.Dir {
        @Name("config.properties")
        S3File config();

        Users users();
    }

    public interface Users extends S3.Dir {
        Stream<UserFile> users();

        UserFile user(String name);
    }

    public interface UserFile extends S3.File {
        default String getUserName() {
            return file().getName().replaceAll("\\.json$", "");
        }
    }
}

The test creates a bucket, populates it with the fluent put() API, then uses a typed proxy to read, list, and write entries — the same code you'd write against real S3. S3Asserts provides a snapshot-based way to verify the final bucket state directly against the underlying S3 client.

Next Steps