Add native S3 Presigned POST URL support to S3Client

Open
#6,577 12 comments 24 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
38/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Quiet
Tech stack
aws, java
Domain
api, cloud

Research direction

Start by reading the S3Presigner and S3Utilities entry points, then compare their signing flow with the S3 RESTObjectPOST API reference. The design still needs an agreed API between the proposed options; done means generating a presigned POST URL and form fields with policy conditions and AWS Signature Version 4 signing.

Written by the indexing model from the issue text.

Description

feature-request
Describe the feature

Description

The AWS SDK for Java V2 should provide native support for generating presigned POST URLs with policy documents directly in the S3Client or S3Presigner, eliminating the need for workarounds or external implementations.

Background

This feature has been requested since 2019 in issue #1493, but remains unimplemented. Currently, developers must resort to workarounds such as:

  • Calling AWS Lambda functions written in other languages (JavaScript/Python)
  • Implementing custom signing logic
  • Using third-party libraries

The V1 SDK also lacked this feature (aws/aws-sdk-java#834), making this a long-standing gap in the Java SDK ecosystem.

Justification

1. Feature Parity with Other SDKs

  • Python (boto3): Provides s3.generate_presigned_post() with policy conditions
  • JavaScript: Supports createPresignedPost() with field policies
  • Go: Includes PresignPostObject() functionality

2. Security & Best Practices
Presigned POST URLs with policy documents enable:

  • Fine-grained access control without exposing AWS credentials
  • Content-type validation to prevent malicious uploads
  • File size limits to prevent abuse
  • Key prefix restrictions to enforce folder structure

Related Issues

  • #1493 - Original feature request (2019)
  • #203 - Related presigning discussion
  • aws/aws-sdk-java#834 - V1 SDK request

I Would Like to Implement This

I am currently implementing S3 POST support in S3 Ninja (an S3-compatible test server) and would be happy to contribute this feature to the SDK. Please assign this issue to me if accepted.

Having this feature would greatly benefit:

  • Developers building modern web applications with direct browser uploads
  • Teams testing S3 integrations locally with tools like S3 Ninja, LocalStack, or MinIO
  • Organizations requiring fine-grained upload control without credential exposure
Use Case

Browser-based direct uploads to S3 are a standard pattern in modern web applications, requiring:

  • Pre-flight authorization checks on the backend
  • Secure, time-limited upload URLs
  • Policy enforcement (file size, content type, key prefix restrictions)

Use Case Example

Backend (Java)
@PostMapping("/upload/presign")
public UploadCredentials getUploadUrl(@RequestBody UploadRequest request) {
    // Validate user permissions
    if (!authService.canUpload(request.getUserId())) {
        throw new ForbiddenException();
    }
    
    // Generate presigned POST
    PresignedPostResponse postResponse = s3Presigner.presignPost(req -> req
        .bucket("user-uploads")
        .key("users/" + request.getUserId() + "/${filename}")
        .expiration(Instant.now().plus(Duration.ofMinutes(15)))
        .conditions(conditions -> conditions
            .contentLengthRange(1024, 5_242_880) // 1KB-5MB
            .startsWith("key", "users/" + request.getUserId())
            .eq("Content-Type", request.getContentType())));
    
    return new UploadCredentials(
        postResponse.url(),
        postResponse.formFields()
    );
}
Frontend (JavaScript)
// Get presigned POST from backend
const { url, fields } = await fetch('/upload/presign', {
    method: 'POST',
    body: JSON.stringify({ userId: '12345', contentType: 'image/jpeg' })
}).then(r => r.json());

// Upload directly to S3
const formData = new FormData();
Object.entries(fields).forEach(([key, value]) => {
    formData.append(key, value);
});
formData.append('file', fileInput.files[0]);

await fetch(url, { method: 'POST', body: formData });
Proposed Solution

Proposed API

Option 1: Extend S3Presigner
S3Presigner presigner = S3Presigner.create();

PresignedPostRequest postRequest = PresignedPostRequest.builder()
    .bucket("my-bucket")
    .key("uploads/${filename}")
    .expiration(Instant.now().plus(Duration.ofMinutes(15)))
    .conditions(PolicyConditions.builder()
        .contentLengthRange(1024, 10_485_760) // 1KB to 10MB
        .startsWith("key", "uploads/")
        .eq("Content-Type", "image/jpeg")
        .eq("x-amz-meta-user-id", "12345")
        .build())
    .build();

PresignedPostResponse response = presigner.presignPost(postRequest);

// Returns:
// - URL: https://my-bucket.s3.amazonaws.com/
// - Form fields: Map<String, String> containing policy, signature, etc.
Option 2: Add to S3Utilities
S3Utilities utilities = S3Utilities.builder()
    .region(Region.US_EAST_1)
    .credentialsProvider(credentialsProvider)
    .build();

PostSigningResult result = utilities.signPostRequest(request -> request
    .bucket("my-bucket")
    .key("uploads/${filename}")
    .expirationTime(Instant.now().plus(Duration.ofMinutes(15)))
    .addCondition("content-length-range", 1024, 10_485_760)
    .addCondition("starts-with", "$key", "uploads/")
    .addCondition("eq", "$Content-Type", "image/jpeg"));

String url = result.url();
Map<String, String> formFields = result.formFields();

Implementation Considerations

Policy Document Structure:

{
  "expiration": "2024-12-01T12:00:00.000Z",
  "conditions": [
    {"bucket": "my-bucket"},
    ["starts-with", "$key", "uploads/"],
    {"Content-Type": "image/jpeg"},
    ["content-length-range", 1024, 10485760],
    {"x-amz-algorithm": "AWS4-HMAC-SHA256"},
    {"x-amz-credential": "AKIAIOSFODNN7EXAMPLE/20241121/us-east-1/s3/aws4_request"},
    {"x-amz-date": "20241121T000000Z"}
  ]
}

Form Fields to Return:

  • key: Object key (can include ${filename} placeholder)
  • policy: Base64-encoded policy document
  • x-amz-algorithm: Signing algorithm
  • x-amz-credential: Credential scope
  • x-amz-date: Request date
  • x-amz-signature: Calculated signature
  • Any additional fields from policy conditions

Signature Calculation:
Should follow AWS Signature Version 4 for POST requests as documented in the S3 API Reference.

Other Information

No response

Acknowledgements
  • I may be able to implement this feature request
  • This feature might incur a breaking change
AWS Java SDK version used

2

JDK version used

24

Operating System and version

Windows 11

Dominant language
Java
Stars
2.6k
Forks
1k
Avg merge
2d 17h
Merged PRs (30d)
39

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from aws/aws-sdk-java-v2

All issues in aws/aws-sdk-java-v2

Similar issues

More Java issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.