Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Generate the PDF first, then upload the resulting file, byte array, or input stream with the AWS SDK for Java 2.x. Use RequestBody.fromFile for a file, RequestBody.fromBytes for an in-memory document, and RequestBody.fromInputStream when you have a stream and its exact length. The S3 upload does not create the PDF; it sends bytes your Java application has already produced.

Choose the upload shape that matches your PDF generator

PDF libraries expose output differently. Select the S3 request body that matches that output rather than converting everything unnecessarily.

PDF output SDK 2.x body Best fit Main caveat
Local Path or file RequestBody.fromFile(path) The generator already writes to disk; large documents Requires temporary-file storage and cleanup
byte[] RequestBody.fromBytes(bytes) Small or moderate PDFs already held in memory Memory use is approximately the PDF size, plus application overhead
InputStream with known size RequestBody.fromInputStream(stream, exactLength) Streaming generation or an existing stream An incorrect length can truncate, stall, or fail the request
Unknown-length stream An SDK-supported buffering provider or multipart design Large streams whose size cannot be known in advance Synchronous buffering can consume the complete stream in memory

SDK 2.x uses RequestBody for synchronous calls and AsyncRequestBody for asynchronous calls. Do not paste SDK 1.x upload signatures into a 2.x project.

Prerequisites and object naming

  • An AWS account, an S3 bucket, and credentials available through the standard AWS credential provider chain (for example, an IAM role, environment variables, or a local profile).
  • A Java project containing the AWS SDK for Java 2.x S3 module. Keep the SDK version aligned with the rest of your dependency management rather than copying an unpinned version into production.
  • An S3 region that matches the bucket, or a client configuration that can resolve the bucket’s region.
  • A unique object key such as invoices/2026/09/invoice-1042.pdf. A key is not a local path; it is the name under which S3 stores the object.

Grant only the permissions required by the application, normally s3:PutObject for the target prefix. Add encryption, tagging, or ACL headers only when your bucket policy requires them.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Upload a generated PDF file synchronously

This is the simplest path when your PDF library writes to a temporary or permanent file. The following SDK 2.x fragment assumes pdfPath already points to a completed PDF.

import java.nio.file.Path;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;

public final class PdfUploader {
    public static void uploadFile(S3Client s3, Path pdfPath,
                                  String bucket, String key) {
        PutObjectRequest request = PutObjectRequest.builder()
            .bucket(bucket)
            .key(key)
            .contentType("application/pdf")
            .build();

        s3.putObject(request, RequestBody.fromFile(pdfPath));
    }
}

Call uploadFile only after the PDF writer has closed its output. Closing the writer first ensures all cross-reference and trailer bytes have reached the file. The method returns after the synchronous operation completes or throws an exception.

Upload a PDF held in a byte array

When the generator returns byte[], avoid writing a needless intermediate file:

import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;

public static void uploadBytes(S3Client s3, byte[] pdf,
                               String bucket, String key) {
    PutObjectRequest request = PutObjectRequest.builder()
        .bucket(bucket)
        .key(key)
        .contentType("application/pdf")
        .build();

    s3.putObject(request, RequestBody.fromBytes(pdf));
}

This approach is straightforward for documents that comfortably fit your heap. For a service handling concurrent reports, account for every simultaneous byte array before choosing this method.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Upload an InputStream with an exact content length

For a stream, the length argument is a correctness requirement, not an estimate. AWS guidance is explicit: “Always provide the exact content length when it’s available.” A value smaller than the actual byte count can truncate the object; a value larger than the bytes available can make the request fail or wait for data that never arrives.

import java.io.InputStream;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;

public static void uploadStream(S3Client s3, InputStream pdfInputStream,
                                long pdfLength, String bucket, String key) {
    PutObjectRequest request = PutObjectRequest.builder()
        .bucket(bucket)
        .key(key)
        .contentType("application/pdf")
        .build();

    s3.putObject(request,
        RequestBody.fromInputStream(pdfInputStream, pdfLength));
}

Do not reuse or close the stream before putObject has returned. Use try-with-resources when your method owns the stream, and close it on both success and failure.

When the stream length is unknown

An unknown-length synchronous provider may buffer the complete stream to determine its length. That can be unsuitable for large PDFs or many concurrent uploads. For large unknown-length data, consider an S3 multipart-upload design or an asynchronous approach documented by the SDK that supports unknown lengths. Choose based on document size, available memory, and whether your request thread can remain occupied.

Asynchronous upload and transfer completion

An asynchronous method returning a future does not mean the object is already durable. Keep and observe the completion stage, and close the client only after the transfer has completed (or after your application has deliberately handed lifecycle ownership elsewhere).

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Async client with a file

import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;
import software.amazon.awssdk.core.async.AsyncRequestBody;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectResponse;

public static CompletableFuture<PutObjectResponse> uploadFileAsync(
        S3AsyncClient s3, Path pdfPath, String bucket, String key) {
    PutObjectRequest request = PutObjectRequest.builder()
        .bucket(bucket)
        .key(key)
        .contentType("application/pdf")
        .build();

    return s3.putObject(request, AsyncRequestBody.fromFile(pdfPath));
}

Use future.join() or, preferably, attach thenAccept and exceptionally handlers according to your application’s execution model. AWS also documents S3 Transfer Manager for file uploads; it exposes a completion future that you should await when subsequent work depends on the object being uploaded.

Metadata, integrity, and lifecycle checks

  • Set contentType("application/pdf") so consumers receive the intended media type.
  • Use deterministic keys only when replacement is intentional. Otherwise include an invoice ID, version, or generated identifier.
  • Never report success merely because PDF generation succeeded. Treat generation and upload as separate states in logs and job records.
  • For retries, use an idempotent key or a run identifier so a retry cannot silently overwrite an unrelated document.
  • Delete temporary files after the upload future completes, not when the request is merely submitted.

Common failures and fixes

AccessDenied

The caller lacks permission for the bucket or key prefix, or a bucket policy denies the request. Check the active credential identity, region, key ARN, and required encryption headers.

NoSuchBucket or wrong region

Verify the bucket name exactly and configure the S3 client for the bucket’s region. A region mismatch can also appear as a redirect or signature error.

Truncated or hanging stream upload

Recalculate the exact byte length from the source. Never use character count for encoded PDF data, and never guess a stream length. If the size cannot be known without buffering, use a design intended for unknown length or multipart upload.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

OutOfMemoryError

A byte-array upload or unknown-length buffering path may hold the complete PDF in memory. Switch to a file-backed upload, bound concurrency, or use multipart transfer.

PDF opens locally but not from S3

Confirm the writer was closed before upload, the object size matches the generated bytes, and the object has application/pdf metadata. A successful HTTP response does not repair an incomplete PDF.

Async application exits early

Keep the completion future, await it, or keep the service alive until the future reports success. Close the asynchronous client after outstanding work is complete.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance and cost decisions

There is no universal fastest method in the available AWS guidance. A local file avoids retaining the entire PDF as a Java array but adds disk I/O. Bytes minimize moving parts for small documents but increase heap pressure. Streams avoid a separate file, yet exact length and buffering behavior become your responsibility. Multipart or asynchronous transfers are architectural choices for large data, not automatic guarantees of a particular throughput.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

S3 charges depend on your AWS account’s region, storage class, request volume, and transferred data. This Java code does not change those service charges. Measure your own workload and account for retries and concurrent jobs when setting timeouts and memory limits.

Or skip the browser setup

If the PDF begins as a webpage capture rather than a server-side PDF document, ScreenshotNeo can return a PDF from one request, after which your Java code can upload the response bytes to S3. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf.

See the ScreenshotNeo API documentation for request options. A Java application can call the PDF endpoint like this (adapt the URL and parameters to your capture):

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.screenshotneo.com/v1/shot?access_key=YOUR_API_KEY&url=https%3A%2F%2Fstripe.com&format=pdf"))
    .GET().build();
byte[] pdf = HttpClient.newHttpClient()
    .send(request, HttpResponse.BodyHandlers.ofByteArray()).body();
Files.write(Path.of("shot.pdf"), pdf);

For a direct command-line call, use curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Should I upload the PDF before closing the generator?

No. Finish and close the PDF writer first so all document structures have been written before the S3 request reads the bytes.

Can I use an SDK 1.x example in an SDK 2.x project?

Not unchanged. SDK 2.x uses RequestBody and AsyncRequestBody; adapt the example to the APIs and modules in your dependency set.

Is a successful putObject call enough for an asynchronous upload?

Only after the returned completion stage reports success. Submission of an asynchronous request is not completion.

The Bottom Line

Generate and finalize the PDF, choose file, bytes, or an exactly sized stream, then wait for synchronous or asynchronous completion before declaring the S3 object ready.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.