Skip to content
Draft
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
15 changes: 14 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
* text eol=lf
# Normalize text files to LF, but let Git auto-detect binaries so they are
# stored byte-for-byte (a blanket "* text" corrupts binaries like the Gradle
# wrapper jar by stripping CR bytes during line-ending normalization).
* text=auto eol=lf

# Always treat these as binary regardless of auto-detection.
*.jar binary
*.zip binary
*.gz binary
*.class binary
*.png binary
*.jpg binary
*.gif binary
*.ico binary
32 changes: 16 additions & 16 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@ jobs:
- name: Check limited Guava usage
if: matrix.os == 'ubuntu-latest'
run: |
if grep --with-filename --line-number --no-messages --recursive --exclude-dir=.github "com.google.common.base.Objects" .; then
if grep --with-filename --line-number --no-messages --recursive --exclude-dir=.github --exclude=CLAUDE.md "com.google.common.base.Objects" .; then
echo "Error: use java.util.Objects instead of com.google.common.base.Objects"
exit 1
fi

- name: Setup java 17 for building
- name: Setup java 25 for building
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '17'
java-version: '25'

- name: Set environment variables on Ubuntu
if: matrix.os == 'ubuntu-latest'
Expand All @@ -59,7 +59,7 @@ jobs:
./gradlew.bat build

- name: Setup java ${{ matrix.java-version }} for testing
if: matrix.java-version != '17'
if: matrix.java-version != '25'
uses: actions/setup-java@v5
with:
distribution: temurin
Expand All @@ -69,20 +69,20 @@ jobs:
if: matrix.os == 'ubuntu-latest'
run: |
cd functional
curl -sSfLO https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/1.11.4/junit-platform-console-standalone-1.11.4.jar
curl -sSfLO https://repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.9.8/spotbugs-annotations-4.9.8.jar
javac -cp spotbugs-annotations-4.9.8.jar:junit-platform-console-standalone-1.11.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:../adminapi/build/libs/minio-admin-${DEV_VERSION}-all.jar:. FunctionalTest.java
java -cp spotbugs-annotations-4.9.8.jar:junit-platform-console-standalone-1.11.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:../adminapi/build/libs/minio-admin-${DEV_VERSION}-all.jar:. FunctionalTest
javac -cp spotbugs-annotations-4.9.8.jar:junit-platform-console-standalone-1.11.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:. ./TestUserAgent.java
java -Dversion=${DEV_VERSION} -cp spotbugs-annotations-4.9.8.jar:junit-platform-console-standalone-1.11.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:. TestUserAgent
javac -cp spotbugs-annotations-4.9.8.jar:junit-platform-console-standalone-1.11.4.jar:../api/build/libs/minio-${RELEASE_VERSION}-all.jar:. ./TestUserAgent.java
java -Dversion=${RELEASE_VERSION} -cp spotbugs-annotations-4.9.8.jar:junit-platform-console-standalone-1.11.4.jar:../api/build/libs/minio-${RELEASE_VERSION}-all.jar:. TestUserAgent
curl -sSfLO https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/1.14.4/junit-platform-console-standalone-1.14.4.jar
curl -sSfLO https://repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.10.2/spotbugs-annotations-4.10.2.jar
javac -cp spotbugs-annotations-4.10.2.jar:junit-platform-console-standalone-1.14.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:../adminapi/build/libs/minio-admin-${DEV_VERSION}-all.jar:. FunctionalTest.java
java -cp spotbugs-annotations-4.10.2.jar:junit-platform-console-standalone-1.14.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:../adminapi/build/libs/minio-admin-${DEV_VERSION}-all.jar:. FunctionalTest
javac -cp spotbugs-annotations-4.10.2.jar:junit-platform-console-standalone-1.14.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:. ./TestUserAgent.java
java -Dversion=${DEV_VERSION} -cp spotbugs-annotations-4.10.2.jar:junit-platform-console-standalone-1.14.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:. TestUserAgent
javac -cp spotbugs-annotations-4.10.2.jar:junit-platform-console-standalone-1.14.4.jar:../api/build/libs/minio-${RELEASE_VERSION}-all.jar:. ./TestUserAgent.java
java -Dversion=${RELEASE_VERSION} -cp spotbugs-annotations-4.10.2.jar:junit-platform-console-standalone-1.14.4.jar:../api/build/libs/minio-${RELEASE_VERSION}-all.jar:. TestUserAgent

- name: Run tests on Windows
if: matrix.os == 'windows-latest'
run: |
cd functional
curl -sSfLO https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/1.11.4/junit-platform-console-standalone-1.11.4.jar
curl -sSfLO https://repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.9.8/spotbugs-annotations-4.9.8.jar
javac -encoding UTF-8 -cp "spotbugs-annotations-4.9.8.jar;junit-platform-console-standalone-1.11.4.jar;../api/build/libs/minio-$Env:DEV_VERSION-all.jar;../adminapi/build/libs/minio-admin-$Env:DEV_VERSION-all.jar;." FunctionalTest.java
java -cp "spotbugs-annotations-4.9.8.jar;junit-platform-console-standalone-1.11.4.jar;../api/build/libs/minio-$Env:DEV_VERSION-all.jar;../adminapi/build/libs/minio-admin-$Env:DEV_VERSION-all.jar;." FunctionalTest
curl -sSfLO https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/1.14.4/junit-platform-console-standalone-1.14.4.jar
curl -sSfLO https://repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.10.2/spotbugs-annotations-4.10.2.jar
javac -encoding UTF-8 -cp "spotbugs-annotations-4.10.2.jar;junit-platform-console-standalone-1.14.4.jar;../api/build/libs/minio-$Env:DEV_VERSION-all.jar;../adminapi/build/libs/minio-admin-$Env:DEV_VERSION-all.jar;." FunctionalTest.java
java -cp "spotbugs-annotations-4.10.2.jar;junit-platform-console-standalone-1.14.4.jar;../api/build/libs/minio-$Env:DEV_VERSION-all.jar;../adminapi/build/libs/minio-admin-$Env:DEV_VERSION-all.jar;." FunctionalTest
61 changes: 61 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Overview

MinIO Java SDK — an S3-compatible client library (`io.minio:minio`) plus a MinIO
admin client (`io.minio:minio-admin`). Multi-module Gradle build. The public API
must remain **Java 8 source/bytecode compatible** even though the build runs on JDK 25.

## Modules (`settings.gradle`)

- `api` — the S3 client. Published as artifact `minio`. The core of the project.
- `adminapi` — MinIO server admin client (`io.minio.admin`). Published as `minio-admin`. Depends on `:api`.
- `functional` — integration tests run against a live S3/MinIO server (`FunctionalTest`, `TestMinioAdminClient`, etc.). Not published; sources live in `functional/` (not `src/`).
- `examples` — runnable usage examples. Sources live in `examples/` (not `src/`).

## Commands

```sh
./gradlew build # compile, run unit tests, spotless check, spotbugs — run before every PR
./gradlew spotlessApply # auto-format (google-java-format); fixes most lint failures
./gradlew :api:test # unit tests for one module
./gradlew :api:test --tests 'io.minio.MinioClientTest' # single test class
./gradlew :api:test --tests 'io.minio.MakeBucketArgsTest.method' # single test method
./gradlew :api:localeTest # re-runs tests under locale de-DE (part of `check`)
./gradlew runFunctionalTest # integration tests vs play.min.io by default
./gradlew runFunctionalTest -Pendpoint=http://localhost:9000 -PaccessKey=... -PsecretKey=... -Pregion=us-east-1
```

Unit tests are JUnit 5 (Jupiter) in `*/src/test/java`. `functional/` tests hit a real server and are not part of `build`.

## Build / lint gates (enforced in CI, fail the build)

- **`--release 8` with `-Werror`** and `-Xlint:unchecked,deprecation`. Any new warning breaks the build. Do not use APIs newer than Java 8 in `api`/`adminapi` main code.
- **Spotless** (`googleJavaFormat`, import order `edu,com,io,java,javax,org`, no unused imports). Run `./gradlew spotlessApply` before committing.
- **SpotBugs** at MAX effort / lowest confidence threshold; suppressions go in `spotbugs-filter.xml`.
- **No `com.google.common.base.Objects`** — CI greps for it; use `java.util.Objects` instead. (Guava is otherwise available.)

## Architecture

**Client layering** (`api/src/main/java/io/minio/`):
- `BaseS3Client` (abstract) — HTTP plumbing, request signing, response handling.
- `MinioAsyncClient extends BaseS3Client` — the real implementation; every operation returns `CompletableFuture`.
- `MinioClient` — synchronous facade that **wraps a `MinioAsyncClient`** and blocks on its futures. It does not extend the async client. A new sync operation almost always means adding the async method first, then a blocking wrapper.
- Both are built via a `.builder()...build()` (endpoint, credentials, region, http client).

**Args pattern** — every public operation takes one immutable `XxxArgs` object built via `XxxArgs.builder()`. The class hierarchy (see `arg-class-structure.txt`) is:
`BaseArgs` → `BucketArgs` (adds bucket/region) → `ObjectArgs` (adds object) → either `ObjectWriteArgs` (uploads: sse, tags, retention, ...) or `ObjectVersionArgs` → `ObjectReadArgs` (ssec) → `ObjectConditionalReadArgs` (offset/length/etag/since). When adding an operation, subclass the right level rather than re-declaring fields.

**Other key packages:**
- `io.minio.messages` — S3 XML request/response models, (de)serialized with simple-xml-safe.
- `io.minio.credentials` — credential `Provider`s (static, env, IAM, assume-role, LDAP, web-identity, certificate, chained).
- `io.minio.errors` — exception hierarchy rooted at `MinioException`.
- `Http`, `Signer` — OkHttp transport and AWS SigV4 signing.

## Conventions

- Public-facing API changes should be reflected in `docs/API.md`.
- Releases are driven by JReleaser (`build.sh` shows the deploy invocation); the `version` lives in `build.gradle` (`-DEV` suffix unless `-Prelease`).
- Target branch for PRs is `master`.
4 changes: 3 additions & 1 deletion adminapi/src/main/java/io/minio/admin/Crypto.java
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,9 @@ public static byte[] encrypt(byte[] payload, String password) throws MinioExcept
boolean done = false;
for (int nonceId = 1; !done; nonceId++) {
int to = from + CHUNK_SIZE;
if (to > payload.length) {
// Use >= so a payload that is an exact multiple of CHUNK_SIZE marks its final full chunk as
// the last one, rather than emitting an extra empty trailing chunk (matches madmin-go/sio).
if (to >= payload.length) {
additionalData = markAsLast(additionalData);
to = payload.length;
done = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,25 @@ public long objectsTotalSize() {
}

public Map<String, BucketTargetUsageInfo> objectsReplicationInfo() {
return Collections.unmodifiableMap(this.objectsReplicationInfo);
return this.objectsReplicationInfo == null
? Collections.emptyMap()
: Collections.unmodifiableMap(this.objectsReplicationInfo);
}

public long bucketsCount() {
return bucketsCount;
}

public Map<String, BucketUsageInfo> bucketsUsageInfo() {
return Collections.unmodifiableMap(this.bucketsUsageInfo);
return this.bucketsUsageInfo == null
? Collections.emptyMap()
: Collections.unmodifiableMap(this.bucketsUsageInfo);
}

public Map<String, Long> bucketsSizes() {
return Collections.unmodifiableMap(bucketsSizes);
return bucketsSizes == null
? Collections.emptyMap()
: Collections.unmodifiableMap(bucketsSizes);
}

public AllTierStats tierStats() {
Expand Down Expand Up @@ -203,7 +209,9 @@ public long objectsCount() {
}

public Map<String, Long> objectsSizesHistogram() {
return Collections.unmodifiableMap(this.objectsSizesHistogram);
return this.objectsSizesHistogram == null
? Collections.emptyMap()
: Collections.unmodifiableMap(this.objectsSizesHistogram);
}

public long versionsCount() {
Expand All @@ -215,7 +223,9 @@ public long objectReplicaTotalSize() {
}

public Map<String, BucketTargetUsageInfo> objectsReplicationInfo() {
return Collections.unmodifiableMap(this.objectsReplicationInfo);
return this.objectsReplicationInfo == null
? Collections.emptyMap()
: Collections.unmodifiableMap(this.objectsReplicationInfo);
}
}

Expand Down Expand Up @@ -249,7 +259,7 @@ public static class AllTierStats {
private Map<String, TierStats> tiers;

public Map<String, TierStats> tiers() {
return Collections.unmodifiableMap(this.tiers);
return this.tiers == null ? Collections.emptyMap() : Collections.unmodifiableMap(this.tiers);
}
}
}
14 changes: 10 additions & 4 deletions adminapi/src/main/java/io/minio/admin/GetServerInfoResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,9 @@ public String commitID() {
}

public Map<String, String> network() {
return Collections.unmodifiableMap(this.network);
return this.network == null
? Collections.emptyMap()
: Collections.unmodifiableMap(this.network);
}

public List<Disk> disks() {
Expand Down Expand Up @@ -321,7 +323,9 @@ public GCStats gCStats() {
}

public Map<String, String> minioEnvVars() {
return Collections.unmodifiableMap(this.minioEnvVars);
return this.minioEnvVars == null
? Collections.emptyMap()
: Collections.unmodifiableMap(this.minioEnvVars);
}

@JsonIgnoreProperties(ignoreUnknown = true)
Expand Down Expand Up @@ -613,11 +617,13 @@ public Integer totalErrorsTimeout() {
}

public Map<String, TimedAction> lastMinute() {
return Collections.unmodifiableMap(lastMinute);
return lastMinute == null
? Collections.emptyMap()
: Collections.unmodifiableMap(lastMinute);
}

public Map<String, String> apiCalls() {
return Collections.unmodifiableMap(apiCalls);
return apiCalls == null ? Collections.emptyMap() : Collections.unmodifiableMap(apiCalls);
}

public Long totalTokens() {
Expand Down
Loading
Loading