An open source, community-built Box API client for Java — one resource manager per
API area behind a single dev.unofficialbox.Client, over a dependency-free
java.net.http runtime with retry, backoff, and token refresh.
Not affiliated with, authorized, or endorsed by Box, Inc. "Box" is a trademark of Box, Inc. This is an independent, generated client.
<dependency>
<groupId>dev.unofficialbox</groupId>
<artifactId>box-open-sdk</artifactId>
<version>0.12.0</version>
</dependency>Authenticate, look up the current user, create a folder, upload a file, extract its fields with Box AI, tag it with metadata, and query for it — end to end. Request bodies use fluent builders:
import dev.unofficialbox.Client;
import dev.unofficialbox.auth.Auth;
import dev.unofficialbox.runtime.Runtime;
import dev.unofficialbox.model.schemas.*;
import java.util.List;
import java.util.Map;
public final class Quickstart {
public static void main(String[] args) throws Exception {
// Client Credentials Grant (server-to-server); developer token, OAuth,
// and JWT also live in dev.unofficialbox.auth.Auth.
var client = new Client(Auth.clientCredentials(
Runtime.CcgConfig.enterprise("CLIENT_ID", "CLIENT_SECRET", "ENTERPRISE_ID")));
// The current user.
var me = client.users.getMe(null);
System.out.println("authenticated as " + me.id());
// Create a folder at the account root ("0").
var folder = client.folders.create(
CreateFolderRequest.builder()
.name("Invoices")
.parent(AttributesParent.builder().id("0").build())
.build(),
null);
// Upload a file into it.
var uploaded = client.uploads.uploadFile(
CreateFileContentRequest.builder()
.attributes(PostFileContentAttributes.builder()
.name("invoice.pdf")
.parent(AttributesParent.builder().id(folder.id()).build())
.build())
.file("<file bytes>".getBytes())
.build(),
null);
var fileId = uploaded.entries().orElseThrow().get(0).id();
// Extract fields from the file with Box AI.
var answer = client.ai.extract(
AiExtract.builder()
.prompt("Extract the invoice number and total amount.")
.items(List.of(
AiItemBase.builder().id(fileId).type(AiCitationType.FILE).build()))
.build());
System.out.println(answer);
// Attach that metadata to the file (an enterprise template).
client.fileMetadata.createFileMetadata(
fileId,
GetFileIdMetadataIdScope.ENTERPRISE,
"invoiceData",
Map.of("invoiceNumber", "INV-0042", "total", 1250));
// Query for files carrying that metadata.
var results = client.search.queryByMetadata(
MetadataQuery.builder()
.from("enterprise_0.invoiceData")
.ancestorFolderId(folder.id())
.build());
System.out.println(results);
}
}BoxChunkedUpload uploads a large file's parts in parallel on virtual
threads — no flags needed:
var file = new dev.unofficialbox.BoxChunkedUpload(client)
.upload(bytes, "large.bin", folderId);BoxStructuredChunkedUpload does the same with structured concurrency, a Java
preview API. Code that uses it passes --enable-preview at both compile
and run time (javac --release 26 --enable-preview …, java --enable-preview …).
A preview class only loads on the JDK release it was compiled for (26); the
default class has no such limit.
API requests prefer HTTP/3: the first request goes over HTTP/2 and later ones
upgrade once Box advertises HTTP/3 (Alt-Svc), falling back to HTTP/2 or
HTTP/1.1. Pick another preference on a configured session:
var session = Runtime.Session.builder(auth)
.httpVersion(java.net.http.HttpClient.Version.HTTP_2)
.build();
var client = new Client(session);The builder also takes maxRetries, default headers, base URLs, or your own
HttpClient (which carries its own version — set one or the other). Token
requests keep their own client.
Requests follow the JVM's default proxy selector: the https.proxyHost /
https.proxyPort / http.nonProxyHosts system properties, not the
HTTPS_PROXY environment variable. To set one in code instead, give it to both
the auth config and the session:
var proxy = java.net.ProxySelector.of(new java.net.InetSocketAddress("proxy.internal", 3128));
var auth = Runtime.clientCredentials(
Runtime.CcgConfig.enterprise(clientId, clientSecret, enterpriseId).proxy(proxy));
var session = Runtime.Session.builder(auth).proxy(proxy).build();With a proxy, the built-in client prefers HTTP/2: HTTP/3 runs over UDP, which an
HTTP proxy can't carry. An injected HttpClient carries its own proxy — set one
or the other.
onRequest and onResponse observe every HTTP attempt — each retry and the
resend after a token refresh included — with its method, URL, headers (the
Authorization value withheld), status, elapsed time, and attempt number:
var session = Runtime.Session.builder(auth)
.onResponse(r -> System.out.println(
r.request().method() + " " + r.status() + " " + r.elapsed() + " #" + r.request().attempt()))
.build();Hooks observe only; they can't change a request. Token requests are not observed.
debugLogging(System.getLogger("dev.unofficialbox")) logs one line per attempt
at DEBUG. Query values whose names look secret (token, secret, password,
code, …) read REDACTED; the token and bodies are never logged.
A 429 is retried for any request; a 5xx or a failed connection only for
idempotent methods, since a POST may already have taken effect. Up to five
retries wait with exponential backoff and full jitter — up to 500ms·2ⁿ before
retry n, capped at 30s — raised to Box's Retry-After when it sends one. A
401 refreshes the token once. Tune the budget and the backoff:
var session = Runtime.Session.builder(auth)
.maxRetries(3)
.retryBackoff(java.time.Duration.ofMillis(200), java.time.Duration.ofSeconds(10))
.build();The docs/ tree carries the per-manager reference — a call snippet for
every method — and the authentication, pagination, and errors guides.