# AWS Pre‑Signed URL

## What is a pre‑signed URL?

*   A pre‑signed URL is a short‑lived request signed by a trusted backend using AWS credentials.
    
*   Grants limited access to a specific S3 object and operation.
    
*   Direct browser-to-S3 uploads and downloads.
    

* * *

## Why use it in production?

This pattern reduces load on your backend and avoids moving large files through API Gateway or your Java service. Instead of acting as a file proxy, your backend acts as a **control plane** that validates the request and generates the URL.

## Benefits

*   Less load on backend servers.
    
*   Faster uploads because the file goes directly to S3.
    
*   Better scalability for large files.
    
*   Lower infrastructure cost.
    
*   Cleaner security model because the client never sees AWS credentials.
    

* * *

## Upload flow

A production-ready flow usually looks like this:

1.  Client sends a request to API Gateway.
    
2.  API Gateway forwards it to your Java Spring Boot backend.
    
3.  Backend validates the user and request.
    
4.  Backend uses AWS SDK to generate a pre‑signed URL.
    
5.  Backend returns the URL to the client.
    
6.  Client uploads the file directly to S3.
    

This means API Gateway is only used for the request to create the URL, not for the actual file transfer.

* * *

## Important limits and assumptions

| Topic | Explanation |
| --- | --- |
| API Gateway payload limit | REST API payloads are limited to **10 MB**, so large files should not be sent through API Gateway. |
| Direct upload pattern | For large files, the client should upload directly to S3 using a pre‑signed URL. |
| Multipart upload | One large file can be split into smaller chunks and uploaded as parts. |
| Maximum multipart parts | S3 multipart upload supports up to **10,000 parts** per upload. |
| URL expiration | Pre‑signed URLs are temporary. AWS SDK / CLI-generated URLs can be valid for up to **7 days**. |
| Browser parallel uploads | Browsers can upload multiple chunks in parallel, but exact concurrency depends on browser and network behavior. |

* * *

## Multipart upload for large files

If the file is large, the best approach is to use multipart upload. The file is split into smaller chunks, and each chunk is uploaded separately using its own pre‑signed URL.

## Why multipart upload helps

*   Faster uploads through parallel requests.
    
*   Better reliability on unstable networks.
    
*   Failed parts can be retried independently.
    
*   Large uploads do not need to restart from the beginning.
    

## Multipart flow

1.  Client asks backend to start an upload.
    
2.  Backend creates a multipart upload in S3.
    
3.  Backend returns an upload ID and pre‑signed URLs for each part.
    
4.  Client uploads each part directly to S3.
    
5.  Client sends the list of uploaded parts back to backend.
    
6.  Backend completes the multipart upload.
    

* * *

## Java package structure

A clean Spring Boot structure could be:

```typescript
com.example.upload
├── controller
│   └── UploadController.java
├── service
│   └── PresignedUrlService.java
├── dto
│   ├── UploadRequest.java
│   └── UploadResponse.java
├── config
│   └── S3Config.java
└── util
    └── S3KeyGenerator.java
```

## How it works

*   `UploadController` receives HTTP requests.
    
*   `PresignedUrlService` contains business logic.
    
*   `S3Config` creates and configures the AWS SDK client.
    
*   DTOs carry request and response data.
    
*   Utility classes generate object keys and filenames.
    

* * *

## Example Java flow

```java
@RestController
@RequestMapping("/api/uploads")
public class UploadController {

    private final PresignedUrlService presignedUrlService;

    public UploadController(PresignedUrlService presignedUrlService) {
        this.presignedUrlService = presignedUrlService;
    }

    @PostMapping("/presigned-url")
    public UploadResponse createUploadUrl(@RequestBody UploadRequest request) {
        return presignedUrlService.generateUploadUrl(request);
    }
}
```

```java
@Service
public class PresignedUrlService {

    private final S3Presigner s3Presigner;

    public PresignedUrlService(S3Presigner s3Presigner) {
        this.s3Presigner = s3Presigner;
    }

    public UploadResponse generateUploadUrl(UploadRequest request) {
        // Validate user, file type, file size
        // Generate unique S3 key
        // Build PutObjectRequest
        // Generate pre-signed URL
        // Return URL + metadata
        return new UploadResponse();
    }
}
```

* * *

## When to use single upload vs multipart

| File size / case | Recommended approach |
| --- | --- |
| Small files | Single pre‑signed URL upload |
| Medium files | Single pre‑signed URL upload if within limits |
| Large files | Multipart upload with pre‑signed URLs |
| Very large files | Multipart upload with parallel chunk uploads |

A good production rule is simple: use the backend for **authorization and URL generation**, and use S3 for the **actual file transfer**.

* * *

## Architecture summary

| Component | Role |
| --- | --- |
| Client | Requests upload permission and sends file directly to S3 |
| API Gateway | Receives API request and forwards it to backend |
| Java Spring Boot backend | Validates request and creates pre‑signed URL |
| AWS SDK / S3Presigner | Signs the S3 request securely |
| Amazon S3 | Stores the uploaded file |

This is the most scalable and secure design for production file uploads.

* * *

## Good practices

*   Keep pre‑signed URL expiry short for security.
    
*   Validate file type and size before generating the URL.
    
*   Use multipart upload for large files.
    
*   Do not proxy large files through your backend.
    
*   Return only the minimum information needed by the client.
    
*   Log upload initiation and completion events in your backend.
    

* * *

* * *

## Final recommendation

For production, this is the best pattern:

*   Use API Gateway only for API calls.
    
*   Use Java backend for validation and signing.
    
*   Upload directly to S3.
    
*   Use multipart upload for large files.
    
*   Keep the URL temporary and tightly scoped.
    

This approach is secure, scalable, and easy to maintain.
