This guide provides step-by-step instructions for upgrading tus-java-server applications and clients to support the official IETF Resumable Uploads for HTTP specification (draft-ietf-httpbis-resumable-upload).
tus-java-server version 2.0.0 introduces full support for the IETF Resumable Uploads standard while maintaining 100% backward compatibility with legacy Tus 1.0.0 clients.
- Zero Breaking Changes for Existing Tus 1.0.0 Clients: Legacy clients sending
Tus-Resumable: 1.0.0header fields will continue to operate without modification. - Dual-Protocol Support: By default,
TusFileUploadServiceautomatically detects the protocol version (ProtocolVersion.AUTO) used by incoming requests and responds appropriately.
You can configure protocol support via the withSupportedProtocolVersions(...) method on TusFileUploadService:
import me.desair.tus.server.ProtocolVersion;
import me.desair.tus.server.TusFileUploadService;
TusFileUploadService tusService = new TusFileUploadService()
.withUploadUri("/files")
.withUploadDeduplication(true)
.withThreadLocalCache(true)
// Enabled by default: AUTO mode detects TUS_1_0_0 vs RUFH per request
.withSupportedProtocolVersions(ProtocolVersion.AUTO);To restrict the server to only IETF Resumable Uploads or legacy Tus 1.0.0:
// Force server to only serve IETF Resumable Uploads for HTTP (RUFH)
tusService.withSupportedProtocolVersions(ProtocolVersion.RUFH);
// Force server to only serve legacy Tus 1.0.0
tusService.withSupportedProtocolVersions(ProtocolVersion.TUS_1_0_0);When updating clients or proxies to use IETF Resumable Uploads, note the following header changes:
| Concept / Header | Tus 1.0.0 | IETF Resumable Uploads |
|---|---|---|
| Version Header | Tus-Resumable: 1.0.0 |
Omitted (protocol autodetected via Upload-Complete) |
| Upload Offset | Upload-Offset: 1234 |
Upload-Offset: 1234 (RFC 9651 Integer) |
| Upload Length | Upload-Length: 5000 |
Upload-Length: 5000 (RFC 9651 Integer) |
| Completeness | Implicitly checked by size | Upload-Complete: ?1 (true) or Upload-Complete: ?0 (false) |
| Partial Upload Media Type | Content-Type: application/offset+octet-stream |
Content-Type: application/partial-upload |
| Upload Limits | Tus-Max-Size: 104857600 |
Upload-Limit: max-size=104857600, max-append-size=5242880 |
| Capabilities Discovery | OPTIONS returns Tus-Version, Tus-Extension |
OPTIONS returns Accept-Patch: application/partial-upload and Upload-Limit |
| Error Format | Plain text | application/problem+json (RFC 7807) |
| Checksum / Data Integrity | Upload-Checksum: sha1 ... |
Content-Digest and Repr-Digest (RFC 9530) |
| Expiration Handling | Upload-Expires: Wed, 25 Jun 2026 16:00:00 GMT |
Upload-Limit: max-age=3600 |
Clients can initiate uploads via POST, PUT, or PATCH. The request must contain the Upload-Complete header:
-
Optimistic Creation (Sending Data Immediately):
POST /files HTTP/1.1 Host: example.com Upload-Length: 123456 Upload-Complete: ?1 Content-Length: 123456 [binary content]
-
Chunked / Multi-Part Creation:
POST /files HTTP/1.1 Host: example.com Upload-Length: 123456 Upload-Complete: ?0
Response:
201 CreatedwithLocation: /files/b530ce8ffandUpload-Limit: max-size=....
Clients send chunk appends using Content-Type: application/partial-upload:
PATCH /files/b530ce8ff HTTP/1.1
Host: example.com
Upload-Offset: 0
Upload-Complete: ?0
Content-Type: application/partial-upload
Content-Length: 50000
[partial binary content]To send the final chunk:
PATCH /files/b530ce8ff HTTP/1.1
Host: example.com
Upload-Offset: 50000
Upload-Complete: ?1
Content-Type: application/partial-upload
Content-Length: 73456
[final binary content]IETF Resumable Upload error responses return RFC 7807 problem details JSON:
- Offset Mismatch (
409 Conflict):{ "type": "https://iana.org/assignments/http-problem-types#mismatching-upload-offset", "title": "offset from request does not match offset of resource", "expected-offset": 12500000, "provided-offset": 25000000 }
In Tus 1.0.0, data integrity was verified using the Upload-Checksum header from the checksum extension.
In RUFH protocol, integrity verification is achieved using RFC 9530 HTTP Digests:
- Chunk verification: Use the
Content-Digestheader containing the cryptographic hash of the transmitted chunk (e.g.Content-Digest: sha-256=:...:). - Full file verification: Use the
Repr-Digestheader in the creation request or final append request to define the expected digest of the complete file. Alternatively, sendWant-Repr-Digestin the request to receive the calculated file digest from the server in the response'sRepr-Digestheader.
The unofficial download extension is fully supported under both protocols. Once enabled via the withDownloadFeature() method:
- Clients can download completed uploads using a standard HTTP
GETrequest to the upload's Location URI, regardless of whether it was uploaded via Tus 1.0.0 or RUFH. - For RUFH download responses, all Tus-specific headers (such as
Upload-MetadataorTus-Extension) are omitted.
The expiration extension works seamlessly across both Tus 1.0.0 and IETF Resumable Uploads protocols when configured via .withUploadExpirationPeriod(Long milliseconds):
- Tus 1.0.0 Protocol: Expiration is communicated to clients via the
Upload-Expiresresponse header field formatted as an HTTP date-time string (RFC 7231, e.g.Upload-Expires: Wed, 25 Jun 2026 16:00:00 GMT). - IETF Resumable Uploads Protocol: Expiration is communicated to clients via the
max-agestructured field parameter in theUpload-Limitresponse header field indicating remaining valid seconds (e.g.Upload-Limit: max-size=1048576, max-age=3600).
Ensure that reverse proxies (Nginx, HAProxy, AWS ALB, Cloudflare):
- Forward custom headers:
Upload-Offset,Upload-Complete,Upload-Length,Upload-Limit,Upload-Draft. - Do not strip or alter
Content-Type: application/partial-upload. - Support HTTP
PATCHandDELETErequests.
Looking for the Java API changes between 1.0.0-3.3 and 2.0.0 (Java 17, Jakarta, default locking, lock-holding streams, storage backends, listeners)? See the Upgrade Guide.