Import files from OpenStack Swift in Java
Download an OpenStack Swift object to a local file using OpenStack4j. The complete Java program below accepts empty objects, checks the HTTP response and downloaded byte count, and keeps the destination untouched until the download succeeds.
Setting up your Java environment
This example uses a POSIX shell on Linux, a UTF-8 locale, and a local filesystem that supports atomic moves. The locale determines how the JVM decodes command-line filenames. Install a maintained JDK 21 and Apache Maven 3.10 separately. The reader path below was tested with OpenJDK 21.0.12.1 and Maven 3.10.0; Maven 3.9.11 also passed.
You also need a Keystone v3 account with project-scoped access to an existing Swift container and object. Have these environment variables available through your provider’s credential setup or secret manager:
| Variable | Value |
|---|---|
OS_AUTH_URL | Keystone URL ending in /v3 |
OS_USERNAME, OS_PASSWORD | Your user name and password |
OS_USER_DOMAIN_NAME | Domain containing the user |
OS_PROJECT_NAME, OS_PROJECT_DOMAIN_NAME | Project and its domain |
OS_REGION_NAME | Region containing the public object-storage endpoint |
The user and project can belong to different domains. Use HTTPS for both Keystone and the Swift endpoint in its service catalog, with certificates trusted by the JVM. Do not commit passwords or put them in copied shell commands. This walkthrough reads an existing object; it does not create containers or change access policies.
Check each executable before creating the project. Paste this block into your existing shell;
the subshell keeps its working directory and options unchanged. An existing swift-import
directory makes setup fail without changing its contents.
(
cd -P . &&
swift_parent=$PWD &&
while :; do
if [ -e "$swift_parent/.mvn" ] || [ -L "$swift_parent/.mvn" ]; then
printf 'Run setup outside an existing Maven project.\n' >&2
exit 1
fi
[ "$swift_parent" = / ] && break
swift_parent=${swift_parent%/*}
[ -n "$swift_parent" ] || swift_parent=/
done &&
command -v java >/dev/null &&
command -v javac >/dev/null &&
command -v mvn >/dev/null &&
command -v locale >/dev/null &&
[ "$(locale charmap)" = UTF-8 ] &&
mkdir swift-import &&
cd swift-import &&
mkdir -p src/main/java
)
Save the following complete file as swift-import/pom.xml. OpenStack4j 3.12 is the published
release in Maven Central.
This artifact includes the RESTEasy connector.
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>swift-import</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.github.openstack4j.core</groupId>
<artifactId>openstack4j</artifactId>
<version>3.12</version>
</dependency>
</dependencies>
<build>
<plugins>
<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.8.1</version>
</plugin>
</plugins>
</build>
</project>
The setup guard refuses ancestor .mvn configuration before creating files. Maven can inherit
launcher options from a surrounding project; inspect those before incorporating this example into
an existing application. If dependency resolution fails, fix network or repository
access and repeat the build command below. Keep the saved source and pom.xml.
Connecting to OpenStack Swift
Save this program as swift-import/src/main/java/SwiftImport.java. It authenticates to Keystone,
scopes the token to the named project and project domain, and selects the requested region. These
are separate choices in Keystone’s identity model.
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import org.openstack4j.api.OSClient.OSClientV3;
import org.openstack4j.api.exceptions.ResponseException;
import org.openstack4j.core.transport.Config;
import org.openstack4j.core.transport.HttpResponse;
import org.openstack4j.model.common.Identifier;
import org.openstack4j.model.storage.object.SwiftObject;
import org.openstack4j.openstack.OSFactory;
public class SwiftImport {
private static String encodeName(String value, boolean object) {
String[] segments = value.split("/", -1);
if (!object && segments.length != 1) {
throw new IllegalArgumentException("A container name cannot contain a slash");
}
for (int index = 0; index < segments.length; index++) {
String segment = segments[index];
if (segment.isEmpty() || segment.equals(".") || segment.equals("..")) {
throw new IllegalArgumentException("Empty and dot path segments are unsupported");
}
segments[index] = URLEncoder.encode(segment, StandardCharsets.UTF_8).replace("+", "%20");
}
return String.join("/", segments);
}
private static String required(String name) {
String value = System.getenv(name);
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("Missing environment variable: " + name);
}
return value;
}
private static long download(OSClientV3 os, String container, String object,
Path destination) throws IOException {
SwiftObject remote = os.objectStorage().objects().get(container, object);
if (remote == null) {
throw new IOException("Object not found");
}
long expected = remote.getSizeInBytes();
Path partial = Files.createTempFile(destination.getParent(), ".swift-import-", ".part");
try {
long written;
// The download API exposes its response without mapping HTTP errors to exceptions.
try (HttpResponse response = remote.download().getHttpResponse()) {
if (response.getStatus() != 200) {
throw new IOException("Download rejected (HTTP " + response.getStatus() + ")");
}
try (InputStream input = response.getInputStream()) {
if (input == null) {
throw new IOException("Missing download stream");
}
written = Files.copy(input, partial, StandardCopyOption.REPLACE_EXISTING);
}
}
if (written != expected) {
throw new IOException("Downloaded byte count does not match object metadata");
}
Files.move(partial, destination,
StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
return written;
} finally {
Files.deleteIfExists(partial);
}
}
public static void main(String[] args) {
if (args.length != 3) {
System.err.println("Usage: SwiftImport CONTAINER OBJECT DESTINATION");
System.exit(2);
}
try {
String container = encodeName(args[0], false);
String object = encodeName(args[1], true);
Config config = Config.newConfig().withConnectionTimeout(10_000);
OSClientV3 os = OSFactory.builderV3()
.endpoint(required("OS_AUTH_URL"))
.credentials(required("OS_USERNAME"), required("OS_PASSWORD"),
Identifier.byName(required("OS_USER_DOMAIN_NAME")))
.scopeToProject(Identifier.byName(required("OS_PROJECT_NAME")),
Identifier.byName(required("OS_PROJECT_DOMAIN_NAME")))
.withConfig(config)
.authenticate()
.useRegion(required("OS_REGION_NAME"));
Path destination = Path.of(args[2]).toAbsolutePath();
long written = download(os, container, object, destination);
System.out.println("Imported " + written + " bytes to " + destination);
} catch (ResponseException error) {
System.err.println("OpenStack request failed (HTTP " + error.getStatus() + ").");
System.exit(1);
} catch (Exception error) {
System.err.println("Import failed (" + error.getClass().getSimpleName()
+ "). Check configuration, object access, and local storage before retrying.");
System.exit(1);
}
}
}
Importing files from Swift
From the directory containing swift-import, paste the following block. Replace exports and
reports/report.bin with your existing container and exact object name. The local destination is
relative to swift-import, and its parent directory must already exist.
Pass raw UTF-8 container and object names, including spaces or literal percent signs. The program
encodes each URL path segment before calling OpenStack4j. Container names cannot contain /;
object paths in this example must have no empty, . or .. segments. Local destination paths
must also use valid UTF-8.
(
cd swift-import &&
mvn -B package dependency:copy-dependencies &&
java -cp 'target/classes:target/dependency/*' SwiftImport \
'exports' 'reports/report.bin' './download.bin'
)
Success prints the imported byte count and absolute destination path. An empty object produces a
zero-byte file and reports zero imported bytes. A failed build prevents the Java command from
running, including when an older compiled class remains in target/.
The program obtains the expected size from Swift’s HEAD response, requires a successful full GET, and compares the copied byte count even when the expected size is zero. Swift documents these operations in its object API reference. Keep the remote object unchanged during the import. A size comparison cannot detect same-size content changes; compare a trusted publisher-supplied checksum separately when you need that check.
An existing destination is replaced only by a completed, size-checked download. The temporary file shares its directory so publication can use an atomic move. If the filesystem cannot perform that move, the import fails without falling back to a non-atomic copy. Java leaves atomic replacement behavior filesystem-dependent; the replacement path here was tested on Linux. Use one importer per destination and keep its parent directory under your control.
Handling large files efficiently
The default RESTEasy connector in OpenStack4j 3.12 buffers the complete response before exposing an input stream. Choose objects that fit comfortably in the JVM’s available memory. Increasing the copy buffer or printing progress from the copy loop does not remove that allocation.
Implementing error handling and retries
The command returns a nonzero status for missing objects, rejected reads, incomplete transfers, and
local write failures. It makes one application-level attempt and requests a 10-second connection
timeout. There is no whole-transfer deadline. In this RESTEasy version,
withReadTimeout configures connection-pool acquisition
instead of a socket-read timeout, so it is omitted here. A stalled response can wait until the
connection closes. Use a supervised process if your application needs an elapsed-time limit.
After a handled failure, fix its cause and repeat the same build-and-run block. The partial file is
removed and a previous destination remains intact when a download fails. A forced process kill or
machine crash can leave a .swift-import-*.part file; remove only the abandoned file belonging to
your stopped import. Do not sweep temporary files while another importer is active.
Error handling considerations
- For an authentication failure, check the Keystone v3 URL, user domain, project domain, and credentials.
- For a missing or denied object, check its exact name and your project’s read access. A successful login does not establish permission to download it.
- For a local I/O failure, check the destination’s existing parent directory, write access, disk space, and support for atomic moves. Avoid deleting a previous destination to work around a failed download.
Performance optimization tips
Keep authentication and downloads on the same thread. OpenStack4j stores the active session in a thread-local value; its threading guide explains how to derive a client from a token on another thread. This single-object command uses one thread and does not implement a worker pool or automatic retries.
For application integration, keep the download, byte check, and publication together. Return the
local path to your consuming code only after download succeeds.
