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.

Generate the PDF first, then upload the resulting bytes, file, or stream to S3. With the AWS SDK for Java 2.x, use RequestBody.fromFile for a PDF already written to disk, RequestBody.fromBytes for a byte array, or RequestBody.fromInputStream when you have a stream and its exact length. For asynchronous transfers, use AsyncRequestBody and wait for the returned completion stage before treating the object as uploaded.

What the upload step does (and does not do)

S3 stores objects; it does not create a PDF from Java objects or HTML. Your application must first call its chosen PDF library and obtain one of these outputs:

  • A local Path or file.
  • A complete byte[] in memory.
  • An InputStream, preferably with a known exact byte length.

You then build a PutObjectRequest, choose a bucket and object key, set metadata such as application/pdf, and provide a matching request body. The examples below use AWS SDK for Java 2.x APIs; SDK 1.x examples use different classes and method signatures.

Choose the upload method that matches your PDF output

PDF output SDK 2.x body Best fit Main consideration
Local file RequestBody.fromFile(path) The generator writes to disk Requires temporary or permanent storage
Byte array RequestBody.fromBytes(bytes) Small or moderate PDFs already in memory Memory use grows with the complete document
InputStream with known size RequestBody.fromInputStream(stream, length) Streaming APIs that expose an exact length The length must equal the bytes that will be read
InputStream with unknown size A content provider that buffers, or multipart/async design Large or genuinely open-ended streams Buffering can consume substantial memory; multipart handling is more involved

Prerequisites

  • An AWS account with an S3 bucket in the region you intend to use.
  • Credentials available through the SDK’s normal provider chain (for example, an IAM role, environment variables, or a configured profile). Do not hard-code access keys in source.
  • The AWS SDK for Java 2.x S3 module, with a version selected by your project’s dependency management.
  • An IAM principal allowed to write objects to the destination bucket and key prefix.

The code deliberately does not pin a dependency version because the correct version is determined by your application’s existing SDK BOM and compatibility requirements.

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

Upload a generated PDF file synchronously

This complete class accepts a PDF path, bucket, key, and optional region from the command line. Your PDF-generation code runs before this class (or writes the path that it receives).

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

public final class UploadPdf {
    public static void main(String[] args) {
        if (args.length < 3) {
            throw new IllegalArgumentException("Usage: UploadPdf <pdf-path> <bucket> <key> [region]");
        }

        Path pdfPath = Paths.get(args[0]);
        String bucket = args[1];
        String key = args[2];
        Region region = args.length > 3 ? Region.of(args[3]) : Region.US_EAST_1;

        PutObjectRequest request = PutObjectRequest.builder()
                .bucket(bucket)
                .key(key)
                .contentType("application/pdf")
                .build();

        try (S3Client s3 = S3Client.builder().region(region).build()) {
            s3.putObject(request, RequestBody.fromFile(pdfPath));
        }

        System.out.println("Uploaded s3://" + bucket + "/" + key);
    }
}

Compile this against the SDK 2.x S3 module and run it after configuring credentials. putObject returns only after the synchronous request completes or throws an exception. Closing the client releases its resources; a long-running service should normally create a client once and reuse it rather than opening one for every PDF.

Upload bytes produced in memory

If the PDF library returns a byte[], use RequestBody.fromBytes. This avoids creating a temporary file but keeps the entire document in memory.

byte[] pdfBytes = createPdfWithYourLibrary();

PutObjectRequest request = PutObjectRequest.builder()
        .bucket(bucketName)
        .key(objectKey)
        .contentType("application/pdf")
        .build();

s3Client.putObject(request, RequestBody.fromBytes(pdfBytes));

Do not call this method with a partial buffer. The array must contain exactly the PDF bytes you want S3 to store. For large documents, a file or streaming strategy can avoid a second full in-memory copy.

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

Upload an InputStream when its length is known

For a stream, pass the exact number of bytes that will be read:

try (InputStream pdfInputStream = openGeneratedPdfStream()) {
    long pdfLength = exactLengthOfTheStream();

    PutObjectRequest request = PutObjectRequest.builder()
            .bucket(bucketName)
            .key(objectKey)
            .contentType("application/pdf")
            .build();

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

Length is a correctness requirement, not an optimization. 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. AWS’s stream-upload guidance is explicit: always provide the exact content length when it is available.

What to do when the stream length is unknown

A synchronous SDK operation still needs a reliable content-length strategy. A content provider that discovers the length may buffer the complete stream in memory, which can be unacceptable for large PDFs. Consider these alternatives:

Write to a temporary file first

Let the PDF generator finish into a temporary Path, obtain the file size, and use RequestBody.fromFile. Delete the file only after putObject succeeds or after your error-handling policy has preserved enough information to retry.

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

Use multipart upload for large content

Multipart upload is designed for objects that are large or whose final size is not available up front. It requires initiating an upload, sending parts, recording their completion information, and completing (or aborting) the upload. The synchronous SDK and AWS transfer utilities provide multipart-capable approaches; select one that matches your stream source and memory budget.

Use an asynchronous body for an asynchronous pipeline

SDK 2.x asynchronous operations use AsyncRequestBody, not the synchronous RequestBody. The body can be backed by a file or a publisher appropriate to your producer. Make sure the producer signals completion and propagates read errors; an asynchronous method call returning does not mean the object is durable in S3.

Asynchronous file upload and completion handling

When the PDF is already on disk and your application should not block its calling thread, use S3AsyncClient. The returned future must be observed:

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

S3AsyncClient s3 = S3AsyncClient.create();
PutObjectRequest request = PutObjectRequest.builder()
        .bucket(bucketName)
        .key(objectKey)
        .contentType("application/pdf")
        .build();

CompletableFuture<PutObjectResponse> upload = s3.putObject(
        request, AsyncRequestBody.fromFile(pdfPath));

upload.whenComplete((response, error) -> {
    if (error != null) {
        logUploadFailure(error);
    } else {
        markUploadComplete(response);
    }
});

// If the current operation must not continue until S3 confirms completion:
upload.join();
s3.close();

In a server, keep the client alive for the service lifetime and close it during shutdown. In a command-line process, waiting on the future before exit prevents the JVM from ending while the transfer is still in progress. AWS also documents S3 Transfer Manager for file uploads; its completion future should be awaited when subsequent work depends on a finished upload.

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

Set object metadata and choose a stable key

Set contentType("application/pdf") so consumers receive the intended media type. Build keys from your application’s identity and retention rules, such as invoices/2026/09/invoice-1234.pdf, rather than from an untrusted filename. S3 keys are not local paths: do not assume that creating a key creates directories, and avoid putting secrets or personal data in a key that appears in logs and URLs.

If an existing key is uploaded again, the new object replaces the previous current version unless your bucket’s versioning policy changes that behavior. If replacement must be prevented, generate unique keys or add an application-level existence check and conflict policy.

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

Reliability, throughput, and cost decisions

Memory

fromBytes is simple, but the complete PDF remains in memory. A file-backed upload shifts storage to disk. Unknown-length synchronous streaming can buffer the entire stream, so estimate the worst-case document size before choosing it.

Latency and concurrency

A synchronous call ties up the calling thread until S3 responds. Asynchronous clients let your application overlap PDF generation and network work, but you still need bounded concurrency so producers do not create more data than the upload path can consume.

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

Completion and retries

Record success only after the synchronous call returns or the asynchronous future completes without an error. On failure, retain the source (file, bytes, or a reproducible generation request) so your retry logic does not attempt to upload an already-consumed stream. Multipart workflows also need explicit abort handling for incomplete uploads.

Billing

S3 charges depend on the storage class, requests, transfer, and region selected for your AWS account. The SDK upload method does not determine a universal price; consult your account’s current S3 pricing and lifecycle configuration when estimating cost.

Troubleshooting common failures

Symptom Likely cause Fix
Access denied The credentials lack permission for the bucket/key, or a bucket policy blocks the request. Check the active credentials, bucket name, region, and the IAM action that permits object writes to that key prefix.
Uploaded PDF is truncated The declared stream length was smaller than the bytes produced. Measure the exact byte count or use a file/byte-array method whose length is known by the SDK.
Request hangs or fails near the end The declared stream length is larger than the bytes available. Fix the length calculation; do not guess. For unknown large streams, use a multipart or asynchronous design.
NoSuchKey when downloading The download key differs from the upload key, including case or prefix. Log the complete s3://bucket/key identifier and use that exact key for retrieval.
Application exits before an async upload finishes The completion future was ignored. Await it with join/get where completion is required, or keep the service alive and handle the callback.
PDF downloads with the wrong media type No content type was supplied, or metadata was set incorrectly. Set contentType("application/pdf") on PutObjectRequest and verify the stored object’s metadata.
SDK compilation errors around request bodies SDK 1.x and 2.x snippets were mixed. Use software.amazon.awssdk 2.x imports consistently; do not combine them with v1 request-body classes.

Or skip the browser setup

If your pipeline also needs clean screenshots of web pages—for example, to archive a rendered invoice preview—ScreenshotNeo can provide the capture step without maintaining browser automation. One GET request returns an image or PDF:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for all request options. Before capture it accepts cookie/consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

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

The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Create a free ScreenshotNeo account.

Frequently Asked Questions

Should I close an S3 client after every PDF?

No. Reuse a client in a long-running service and close it during application shutdown. A short-lived command-line process can use try-with-resources.

Can the upload call generate the PDF for me?

No. PDF generation remains a separate application step; S3 receives the file, bytes, or stream that your Java code supplies.

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.

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