Java Serverless on AWS Lambda · Capstone Case Studies

Capstone: Event-Driven Invoice Processing in Java

Design a Java serverless pipeline that turns supplier invoices into payments without duplicates or lost documents.

Scenario

Example company: a logistics firm receives 30,000 supplier invoices a day as PDFs and XML files uploaded to S3 by suppliers. Each invoice must be parsed, validated against purchase orders in PostgreSQL, approved automatically (or sent for manual review) and paid through a bank API that allows 50 requests per second and supports idempotency keys. The team writes Java 21 and wants a serverless design.

Requirements

  • Every uploaded invoice is either paid, rejected with a reason, or queued for manual review, never silently lost.
  • No invoice is ever paid twice.
  • Most invoices are paid within 30 minutes of upload; month-end peaks are 5x normal volume.
  • Auditors can see the full history of each invoice.

Constraints

  • PostgreSQL (RDS, 200 connections) holds purchase orders; DynamoDB is available for new tables.
  • Parsing some PDFs takes up to 40 seconds and 1.5 GB of memory.
  • Suppliers sometimes upload the same file twice.

Deliverables

  1. The architecture (services, functions, queues or workflows) and the invoice state model.
  2. Duplicate and idempotency handling from upload to payment.
  3. Throughput and protection of PostgreSQL and the bank API, including month-end peaks.
  4. Java implementation choices: runtime, cold starts, memory, libraries and how parsing is isolated.
  5. Audit and observability.

Hints

  • Step Functions Standard workflows keep an execution history per item.
  • An S3 object's ETag or a hash of its content identifies duplicates better than its key.
  • Queue-level concurrency is a natural throttle for a rate-limited partner.

Draft each deliverable below before revealing the model answer and rubric.

Further reading: What is Step Functions? · Using Lambda with Amazon SQS

Deliverables

Describe the architecture and state model.

Model answer

  • S3 upload (prefix-filtered) triggers a small Java function that computes a content hash, writes an Invoice item in DynamoDB (RECEIVED) with a conditional put on the hash, and starts a Step Functions Standard execution named after the invoice ID.
  • Workflow steps: Parse (separate function, 2 GB memory, 60 s timeout) -> Validate against purchase orders (function using RDS Proxy) -> Decide (auto-approve rules or manual review task with a callback token) -> Pay (enqueue to a payments SQS queue) -> Record result.
  • States: RECEIVED, PARSED, VALIDATION_FAILED, NEEDS_REVIEW, APPROVED, PAYMENT_PENDING, PAID, REJECTED; transitions are conditional writes.

Rubric

Look for an explicit state machine with every outcome represented, isolation of heavy parsing, and a manual review path.

Handle duplicates and idempotency end to end.

Model answer

  • Upload duplicates: conditional put on the content hash rejects a second invoice item; the second S3 event finds the existing record and stops.
  • Workflow duplicates: execution name = invoice ID, so a second start for the same invoice fails.
  • Payment: the payments worker calls the bank with idempotency key = invoice ID; the status update to PAID is conditional on PAYMENT_PENDING; SQS redeliveries are therefore harmless.

Rubric

Strong answers handle duplicates at each boundary (upload, workflow start, payment) using database conditions and partner idempotency keys.

Protect PostgreSQL and the bank API.

Model answer

  • Validation functions connect through RDS Proxy with reserved concurrency (for example 40) so connections stay well under 200.
  • The payments queue's event source maximum concurrency and batch size are set so that calls stay under 50 per second (for example 10 concurrent workers each making about 4 calls per second), with backoff on 429s.
  • Month-end peaks queue up and drain at the safe rate; the 30-minute target is monitored and capacity agreed with the bank if needed.

Rubric

Look for concrete numbers derived from the 200-connection and 50-rps limits, and for queues used as buffers.

Make Java implementation choices.

Model answer

  • Java 21 managed runtime with SnapStart on latency-sensitive functions (upload handler, validation); parsing is throughput-bound, so cold starts matter less there.
  • Lean artifacts per function (specific SDK modules, CRT HTTP client); Jackson streaming or a dedicated PDF library only in the parse function.
  • Parsing memory sized from measurements (1.5 GB working set plus non-heap headroom, so 2-3 GB), with a timeout well above the slowest documents and a fallback to manual review on parse failure.
  • Virtual threads for concurrent purchase-order lookups within validation.

Rubric

Good answers size and configure each function for its workload rather than one setting for all, and isolate heavy dependencies.

Provide audit and observability.

Model answer

  • Step Functions execution history plus an append-only InvoiceEvents table (or the DynamoDB stream into S3) give auditors every state change with timestamps and actors (including manual reviewers).
  • Powertools (Java) structured logs with invoice ID; metrics for invoices per state, time from upload to paid, review backlog, payment failures.
  • Alarms: invoices older than 30 minutes not in a terminal or review state, DLQ messages, failed executions, bank 4xx/5xx rates.

Rubric

Look for per-invoice traceability and an age-based alarm that catches stuck invoices, not only error alarms.

Open in the app to mark lessons complete, take notes and track your progress.