Import files from MinIO in Java
Download a MinIO object into a temporary file, then publish it to the requested destination after reading and closing the response. This Java command keeps an existing destination intact and exits with status 1 if the import fails. A local demo uploads known binary bytes first, so you can check what the importer actually saved.
Setting up your Java environment
This walkthrough uses the MinIO Java SDK for a self-hosted endpoint and one exact object key. For AWS credentials, regions, and transfer-manager behavior, see the separate Amazon S3 Java guide.
Use Linux, Bash, OpenJDK 21.0.12.1, and Maven 3.9.16. The example uses
MinIO Java SDK 9.0.3. Maven downloads the
SDK and build plugins from Maven Central. Set JAVA_HOME to your JDK installation if Maven uses a
different Java version.
For the disposable local demo, also have cURL with AWS Signature V4 support and a minio server
binary on PATH. The replay used cURL 8.22.0 and MinIO built from revision 9e49d5e7a648.
MinIO Community Edition is archived and no longer maintained.
This fixture is for local learning, not a recommendation for a new production deployment. If you
already manage a MinIO service, you can use the importer directly with that service instead.
Choose a writable parent directory. Paste this block there; it creates a new project and leaves
your shell in the parent. If minio-import already exists, it stops before writing inside it.
(
set -eu
mkdir minio-import
cd minio-import
mkdir -p .mvn src/main/java
printf '<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"/>\n' > settings.xml
)
Save the following files inside minio-import. Its own .mvn directory prevents Maven from using
an enclosing project's .mvn configuration. The launcher uses empty settings and a project-local
.m2 repository, so it does not update your usual Maven dependency cache.
Integrating the MinIO Java SDK
Save this complete pom.xml. The compiler release and plugin versions are explicit; no enclosing
parent POM is required. OkHttp 5 needs its JVM artifact explicitly in Maven;
okhttp-jvm here matches the version used by the SDK.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>minio-import</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>9.0.3</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp-jvm</artifactId>
<version>5.3.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.9.0</version>
</plugin>
</plugins>
</build>
</project>
Importing files from MinIO: a step-by-step guide
Save this as src/main/java/MinIOFileImporter.java. The arguments are bucket, exact object key,
and local destination. Pass the key as stored, such as reports/April report.bin; do not turn it
into a URL or percent-encode it yourself.
import io.minio.GetObjectArgs;
import io.minio.GetObjectResponse;
import io.minio.MinioClient;
import io.minio.errors.ErrorResponseException;
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
public class MinIOFileImporter {
public static void main(String[] args) {
try {
long bytes = download(args);
System.out.println("Imported " + bytes + " bytes.");
} catch (FileAlreadyExistsException e) {
System.err.println("Destination exists; choose a new path.");
System.exit(1);
} catch (ErrorResponseException e) {
System.err.println("Storage refused the download (HTTP " + e.response().code() + ").");
System.exit(1);
} catch (Exception e) {
System.err.println("Import failed (" + e.getClass().getSimpleName() + "). Check configuration, network, and disk.");
System.exit(1);
}
}
private static String required(String name) {
String value = System.getenv(name);
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("Missing " + name);
}
return value;
}
private static long download(String[] args) throws Exception {
if (args.length != 3 || args[0].isBlank() || args[1].isEmpty() || args[2].isBlank()) {
throw new IllegalArgumentException("Expected bucket, object key, and destination");
}
String endpoint = required("MINIO_ENDPOINT");
String region = required("MINIO_REGION");
String accessKey = required("MINIO_ACCESS_KEY");
String secretKey = required("MINIO_SECRET_KEY");
Path destination = Path.of(args[2]).toAbsolutePath();
if (Files.exists(destination, LinkOption.NOFOLLOW_LINKS)) {
throw new FileAlreadyExistsException(destination.toString());
}
if (!Files.isDirectory(destination.getParent())) {
throw new IOException("Destination parent must already exist");
}
Path staged = Files.createTempFile(destination.getParent(), ".minio-", ".part");
try {
long copied;
try (MinioClient client = MinioClient.builder()
.endpoint(endpoint).region(region).credentials(accessKey, secretKey).build()) {
client.setTimeout(10_000, 30_000, 30_000);
try (GetObjectResponse response = client.getObject(
GetObjectArgs.builder().bucket(args[0]).object(args[1]).build())) {
String length = response.headers().get("Content-Length");
if (length == null) throw new IOException("Missing Content-Length");
long expected = Long.parseLong(length);
copied = Files.copy(response, staged, StandardCopyOption.REPLACE_EXISTING);
if (expected < 0 || copied != expected) {
throw new IOException("Incomplete response body");
}
}
}
Files.move(staged, destination);
return copied;
} finally {
Files.deleteIfExists(staged);
}
}
}
The SDK's getObject contract
requires closing the returned response. Here, both response and client close before publication.
An empty object is valid and produces a zero-byte file. The length comparison detects an incomplete
body; it is not an independent checksum or proof that the server stored the intended content.
Save download.sh in the project root. Always run it from that directory. It builds the published
class and copies runtime dependencies before launching Java. The shell stops on a failed build,
even if an older compiled class remains.
#!/usr/bin/env bash
set -eu
if [ ! -d .mvn ] || [ ! -f settings.xml ] || [ ! -f src/main/java/MinIOFileImporter.java ]; then
printf 'Run download.sh from the minio-import project root.\n' >&2
exit 1
fi
if [ "$#" -ne 3 ]; then
printf 'Usage: bash download.sh BUCKET OBJECT_KEY DESTINATION\n' >&2
exit 1
fi
MAVEN_SKIP_RC=1 MAVEN_ARGS= MAVEN_OPTS= MAVEN_BASEDIR="$PWD" \
mvn --batch-mode --no-transfer-progress --settings settings.xml \
--global-settings settings.xml -Dmaven.repo.local="$PWD/.m2" \
compile dependency:copy-dependencies -DincludeScope=runtime
java -cp 'target/classes:target/dependency/*' MinIOFileImporter "$@"
Use a local directory you control and run one importer per destination. The move omits
REPLACE_EXISTING, following Java's move policy.
This sequential recipe does not promise concurrent publication or crash durability. Handled
transfer failures remove staging files and leave the destination absent. A killed JVM can leave a
.minio-*.part file; remove it only after confirming the importer has stopped. A filesystem failure
at publication or cleanup can require inspecting the directory before retrying.
Verify a local object from upload to download
Save demo.sh in the project root. It starts a private loopback server with disposable credentials,
uploads an eight-byte binary fixture through cURL, and invokes download.sh. It keeps the fixture,
server log, server data, and downloaded file for inspection, and stops its server on exit. It does
not contact a public demo endpoint.
#!/usr/bin/env bash
set -eu
port=${1:-19000}
case "$port" in ''|*[!0-9]*) printf 'Supply a numeric local port.\n' >&2; exit 1;; esac
if [ "$port" -lt 1024 ] || [ "$port" -gt 65535 ]; then
printf 'Use a local port from 1024 through 65535.\n' >&2
exit 1
fi
if [ ! -f download.sh ] || [ ! -d .mvn ]; then
printf 'Run demo.sh from the minio-import project root.\n' >&2
exit 1
fi
command -v minio >/dev/null
command -v curl >/dev/null
mkdir demo-data
mkdir demo-certs
export MINIO_ENDPOINT="http://127.0.0.1:$port" MINIO_REGION=us-east-1
export MINIO_ACCESS_KEY=local-demo MINIO_SECRET_KEY=local-demo-secret
MINIO_ROOT_USER="$MINIO_ACCESS_KEY" MINIO_ROOT_PASSWORD="$MINIO_SECRET_KEY" \
MINIO_BROWSER=off minio server demo-data --certs-dir demo-certs \
--address "127.0.0.1:$port" \
> demo-server.log 2>&1 &
server_pid=$!
stop_server() {
kill "$server_pid" 2>/dev/null || :
wait "$server_pid" 2>/dev/null || :
}
trap stop_server EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
ready=0
for attempt in {1..100}; do
if ! kill -0 "$server_pid" 2>/dev/null; then
printf 'MinIO startup failed; inspect demo-server.log.\n' >&2
exit 1
fi
if grep -q '^API:' demo-server.log && \
curl -fsS --max-time 1 "$MINIO_ENDPOINT/minio/health/ready" >/dev/null 2>&1; then
ready=1
break
fi
sleep 0.1
done
if [ "$ready" -ne 1 ]; then
printf 'MinIO did not become ready; inspect demo-server.log.\n' >&2
exit 1
fi
printf '\000\377MinIO\n' > sample.bin
curl -fsS --max-time 10 --aws-sigv4 'aws:amz:us-east-1:s3' \
--user "$MINIO_ACCESS_KEY:$MINIO_SECRET_KEY" -X PUT "$MINIO_ENDPOINT/demo-bucket"
curl -fsS --max-time 10 --aws-sigv4 'aws:amz:us-east-1:s3' \
--user "$MINIO_ACCESS_KEY:$MINIO_SECRET_KEY" --upload-file sample.bin \
"$MINIO_ENDPOINT/demo-bucket/fixtures/sample.bin"
bash download.sh demo-bucket 'fixtures/sample.bin' ./downloaded.bin
cmp sample.bin downloaded.bin
printf 'Verified downloaded.bin against sample.bin.\n'
cURL's --aws-sigv4 option signs the fixture's
bucket-creation and upload requests independently of the Java downloader. Choose an unused local
port and paste this from the parent directory:
(cd minio-import && bash demo.sh 19000)
After Maven's build output, expect these lines and exit status 0:
Imported 8 bytes.
Verified downloaded.bin against sample.bin.
Repeating the complete demo stops at mkdir demo-data and returns failure, preserving the previous
files. Use a new project directory for another complete demo. The importer itself also refuses an
existing downloaded.bin; it never removes that file to make a retry succeed.
Use your existing MinIO endpoint
Provide MINIO_ENDPOINT, MINIO_REGION, MINIO_ACCESS_KEY, and MINIO_SECRET_KEY through your
process environment, using credentials with s3:GetObject permission for the selected object. Use
the S3 API endpoint, not the web console address. Keep the region consistent with your server.
HTTP and the root credentials above belong only to the loopback fixture; use HTTPS and your
organization's credential mechanism for your managed service. Do not disable certificate checking
to work around a trust-store error.
Once the environment is configured, paste this from the project root, replacing the bucket and key with an object that already exists:
bash download.sh my-bucket 'reports/April report.bin' './April report.bin'
Handling common exceptions
Status 0 means the full response was read, resources closed, and the destination published. Status 1 means the command failed; the diagnostic deliberately omits credentials, response bodies, and stack traces. An HTTP 404 can indicate a missing bucket or exact key. An HTTP 403 can indicate invalid credentials or a denied object. Local path errors and incomplete transfers also fail.
The connection timeout is 10 seconds and the read/write timeouts are 30 seconds, using the
SDK's millisecond timeout parameters.
These are operation timeouts, not a deadline for the whole download. There is no application retry
loop: after a transient failure, rerun the whole command with an absent destination. Each attempt
starts a fresh staging file. Adding a retry around getObject alone would miss failures while
reading its body.
Troubleshooting
Connection refused
Check the API host and port, whether the server is running, and whether your machine can reach it.
For the local demo, inspect demo-server.log; an occupied port must be changed before starting
another fixture. A failed build runs no download, so resolve Maven's diagnostic first.
Access denied
Check the account's object permission, the region, and the exact case and punctuation of the key. The importer does not list a prefix or choose the first matching object. If a destination already exists, select a new local filename rather than deleting an unverified earlier result.
For an import that feeds a hosted processing pipeline, the 🤖 /minio/import Robot is a separate integration option. The local command above produces a file for your own Java workflow.
