Importar archivos de MinIO en Java
Descarga un objeto de MinIO en un archivo temporal y publícalo en el destino solicitado después de leer y cerrar la respuesta. Este comando Java mantiene intacto cualquier destino existente y sale con el estado 1 si falla la importación. Una demostración local sube primero bytes binarios conocidos para que puedas comprobar qué guardó realmente el importador.
Prepara tu entorno Java
Este tutorial usa el SDK de MinIO para Java con un endpoint autoalojado y una clave de objeto exacta. Para obtener información sobre credenciales de AWS, regiones y el comportamiento del gestor de transferencias, consulta la guía de Amazon S3 para Java.
Usa Linux, Bash, OpenJDK 21.0.12.1 y Maven 3.9.16. El ejemplo usa
MinIO Java SDK 9.0.3. Maven descarga el SDK y los plugins de
compilación de Maven Central. Configura JAVA_HOME con la ruta de tu instalación del JDK
si Maven usa una versión distinta de Java.
Para la demostración local desechable, también necesitas cURL con soporte para AWS Signature V4 y un
binario del servidor minio en PATH.
La repetición de la prueba usó cURL 8.22.0 y MinIO compilado a partir de la revisión 9e49d5e7a648.
MinIO Community Edition está archivado y ya no recibe mantenimiento.
Este entorno de prueba es para aprender de forma local, no una recomendación para un nuevo despliegue
en producción. Si ya administras un servicio de MinIO, puedes usar el importador directamente con ese
servicio.
Elige un directorio padre con permisos de escritura. Pega este bloque allí; crea un proyecto nuevo y
mantiene tu shell en el directorio padre. Si minio-import ya existe, se detiene antes de
escribir en él.
(
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
)
Guarda los siguientes archivos dentro de minio-import.
Su propio directorio .mvn impide que Maven use la configuración
.mvn de un proyecto que lo contenga. El lanzador usa una configuración vacía y un
repositorio .m2 local del proyecto, por lo que no actualiza tu caché habitual de
dependencias de Maven.
Integra el SDK de MinIO para Java
Guarda este pom.xml completo. La versión objetivo del compilador y las versiones de
los plugins son explícitas; no se requiere un POM padre de un proyecto que lo contenga.
OkHttp 5 necesita su artefacto JVM de forma explícita en Maven;
aquí, okhttp-jvm coincide con la versión que usa el 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>
Importa archivos de MinIO paso a paso
Guarda esto como src/main/java/MinIOFileImporter.java. Los argumentos son el bucket, la clave exacta del objeto y
el destino local. Pasa la clave tal como está almacenada, por ejemplo,
reports/April report.bin; no la conviertas en una URL ni le apliques codificación porcentual por tu cuenta.
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);
}
}
}
El contrato de getObject del SDK
exige cerrar la respuesta devuelta. Aquí, tanto la respuesta como el cliente se cierran antes de la
publicación. Un objeto vacío es válido y produce un archivo de cero bytes. La comparación de longitud
detecta un cuerpo incompleto; no es una suma de verificación independiente ni una prueba de que el
servidor haya almacenado el contenido previsto.
Guarda download.sh en la raíz del proyecto. Ejecútalo siempre desde ese directorio.
Compila la clase publicada y copia las dependencias de ejecución antes de iniciar Java. El shell se
detiene si falla la compilación, incluso si queda una clase compilada anterior.
#!/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 "$@"
Usa un directorio local que controles y ejecuta un importador por destino. La operación de movimiento
omite REPLACE_EXISTING, de acuerdo con la
política de movimiento de Java.
Esta guía secuencial no promete publicación concurrente ni durabilidad ante fallos del sistema.
Los fallos de transferencia gestionados eliminan los archivos temporales y dejan el destino sin crear.
Una JVM terminada de forma forzada puede dejar un archivo .minio-*.part; elimínalo solo
después de confirmar que el importador se ha detenido. Un fallo del sistema de archivos durante la
publicación o la limpieza puede requerir que inspecciones el directorio antes de volver a intentarlo.
Verifica un objeto local desde la subida hasta la descarga
Guarda demo.sh en la raíz del proyecto. Inicia un servidor privado en la interfaz de
bucle local con credenciales desechables, sube datos binarios de prueba de ocho bytes mediante cURL e
invoca download.sh. Conserva los datos de prueba, el registro del servidor, los datos del
servidor y el archivo descargado para su inspección, y detiene su servidor al salir. No se conecta a
ningún endpoint público de demostración.
#!/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'
La opción --aws-sigv4 de cURL firma las
solicitudes de creación del bucket y de subida de los datos de prueba de forma independiente del
programa de descarga en Java. Elige un puerto local libre y pega esto desde el directorio padre:
(cd minio-import && bash demo.sh 19000)
Después de la salida de compilación de Maven, deberías ver estas líneas y el estado de salida 0:
Imported 8 bytes.
Verified downloaded.bin against sample.bin.
Al repetir la demostración completa, esta se detiene en mkdir demo-data y devuelve un error,
conservando los archivos anteriores. Usa un directorio de proyecto nuevo para otra demostración
completa. El propio importador también rechaza un downloaded.bin existente; nunca elimina
ese archivo para que un reintento tenga éxito.
Usa tu endpoint de MinIO existente
Proporciona MINIO_ENDPOINT, MINIO_REGION, MINIO_ACCESS_KEY y MINIO_SECRET_KEY
a través del entorno de tu proceso, con credenciales que tengan el permiso
s3:GetObject para el objeto seleccionado. Usa el endpoint de la API de S3, no la dirección
de la consola web. Mantén la región en consonancia con tu servidor. HTTP y las credenciales de
administrador anteriores son solo para el entorno de prueba en bucle local; usa HTTPS y el mecanismo
de credenciales de tu organización para el servicio que administras. No desactives la verificación de
certificados para eludir un error del almacén de confianza.
Una vez configurado el entorno, pega esto desde la raíz del proyecto y sustituye el bucket y la clave por los de un objeto que ya exista:
bash download.sh my-bucket 'reports/April report.bin' './April report.bin'
Gestiona las excepciones habituales
El estado 0 significa que se leyó la respuesta completa, se cerraron los recursos y se publicó el destino. El estado 1 significa que el comando falló; el diagnóstico omite deliberadamente las credenciales, los cuerpos de las respuestas y las trazas de pila. Un HTTP 404 puede indicar que no existe el bucket o la clave exacta. Un HTTP 403 puede indicar credenciales no válidas o acceso denegado al objeto. Los errores de rutas locales y las transferencias incompletas también provocan un fallo.
El tiempo de espera de conexión es de 10 segundos y los de lectura y escritura son de 30 segundos,
mediante los parámetros de tiempo de espera en milisegundos del SDK.
Estos tiempos de espera corresponden a operaciones, no son un plazo límite para toda la descarga.
No hay un bucle de reintentos en la aplicación: después de un fallo transitorio, vuelve a ejecutar el
comando completo con un destino que no exista. Cada intento comienza con un archivo temporal nuevo.
Añadir un reintento solo alrededor de getObject no cubriría los fallos durante la
lectura de su cuerpo.
Solución de problemas
Conexión rechazada
Comprueba el host y el puerto de la API, si el servidor está en ejecución y si tu equipo puede
conectarse a él. Para la demostración local, inspecciona demo-server.log; si el puerto está
ocupado, debes cambiarlo antes de iniciar otro entorno de prueba. Si la compilación falla, no se
ejecuta ninguna descarga, así que resuelve primero el diagnóstico de Maven.
Acceso denegado
Comprueba el permiso de la cuenta sobre el objeto, la región y las mayúsculas, minúsculas y signos de puntuación exactos de la clave. El importador no enumera un prefijo ni elige el primer objeto que coincida. Si ya existe un destino, elige un nombre de archivo local nuevo en lugar de eliminar un resultado anterior sin verificar.
Para una importación que alimente un pipeline de procesamiento alojado, el Robot 🤖 /minio/import es una opción de integración independiente. El comando local anterior produce un archivo para tu propio flujo de trabajo en Java.
