Java & Spring Boot Integration Plan
Java 17+, Spring Boot 3.x+, Maven, AWS SDK for Java v2. SDK v1 appears only when discussing legacy migration, and is labelled as such.
1. The running example
One application evolves across all 14 days rather than 14 disconnected snippets. This is deliberate: the learner sees the same service acquire AWS capabilities, and every new service is introduced by a problem the existing code has.
flowchart LR
D3["D3 · order-service<br/>on EC2, reads S3"] --> D4["D4 · containerized<br/>on ECS behind ALB"]
D4 --> D6["D6 · persists to Aurora<br/>secret from Secrets Manager"]
D6 --> D7["D7 · DynamoDB for<br/>idempotency + Redis cache"]
D7 --> D8["D8 · publishes to SQS<br/>worker consumes idempotently"]
D8 --> D9["D9 · emits domain events<br/>to EventBridge"]
D9 --> D10["D10 · presigned uploads<br/>+ CloudFront delivery"]
D10 --> D11["D11 · least-privilege roles<br/>KMS, JWT auth"]
D11 --> D13["D13 · traced, structured logs<br/>custom metrics"]
D13 --> D14["D14 · capstone"]By Day 14 the learner has a repository whose commit history is the course.
2. The eleven SDK integrations taught in depth
For each, the course covers the same nine points: Maven dependency · configuration · client creation · authentication · request · response · error handling · retry/timeout · production concerns.
| # | Integration | Day | The specific thing that makes it non-trivial |
|---|---|---|---|
| 1 | STS — who am I | 1 | The credential provider chain, made visible |
| 2 | S3 basic | 3 | Instance role credentials, no config |
| 3 | CloudWatch logs + metrics | 3, 13 | Structured JSON, EMF vs PutMetricData, Micrometer |
| 4 | Secrets Manager | 6 | Caching the secret, handling rotation without a restart |
| 5 | Aurora / JDBC | 6 | HikariCP sizing, failover-aware config, JVM DNS TTL |
| 6 | DynamoDB Enhanced Client | 7 | Bean mapping, GSI queries, conditional writes, TransactWriteItems |
| 7 | ElastiCache / Redis | 7 | Spring Data Redis, serialization, TTL jitter, cluster-mode client config |
| 8 | SQS | 8 | Long polling, batch receive, visibility heartbeats, manual vs Spring Cloud AWS listener |
| 9 | SNS / EventBridge | 8, 9 | Event envelope design, PutEvents batching and partial failures |
| 10 | S3 advanced | 10 | Transfer Manager, multipart, presigned URL generation and expiry |
| 11 | Lambda handler | 5 | Static init for client reuse, RequestHandler typing, cold-start budget |
3. Fixed conventions across every code sample
Dependencies — always via the BOM
<dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>${aws.sdk.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>Individual artifacts are then declared without versions. Every sample states the SDK version it was written against and the date.
Clients are singletons
Every client is a Spring @Bean created once. The course explains why with the cost: each client builds a connection pool, resolves credentials, and (for some) loads region metadata — creating one per request is a latency and file-descriptor bug, and inside Lambda it converts a warm invocation into something close to a cold one.
Configuration is explicit
Every client sample sets, and explains, the four things most code leaves at defaults:
@Bean
SqsClient sqsClient() {
return SqsClient.builder()
.region(Region.of(region)) // explicit, never inferred in prod
.credentialsProvider(DefaultCredentialsProvider.create())
.overrideConfiguration(c -> c
.apiCallTimeout(Duration.ofSeconds(10)) // total, including retries
.apiCallAttemptTimeout(Duration.ofSeconds(3))// per attempt
.retryStrategy(RetryMode.STANDARD)) // backoff + jitter
.httpClientBuilder(ApacheHttpClient.builder()
.maxConnections(100)) // sized against your thread pool
.build();
}The course teaches the distinction between apiCallTimeout and apiCallAttemptTimeout explicitly — confusing them is why "we set a 2-second timeout" turns into a 10-second p99.
Error handling is typed
Never catch (Exception e). Every sample distinguishes:
| Category | Example | Correct response |
|---|---|---|
| Throttling / transient | ProvisionedThroughputExceededException, RequestLimitExceeded, 5xx | Retry with backoff — usually already handled by the SDK; know when it isn't |
| Business-conditional | ConditionalCheckFailedException | Not an error — it's the answer. Often means "already processed." |
| Client error | AccessDeniedException, ValidationException | Fail fast, alert, do not retry |
| Not found | NoSuchKeyException | Domain decision |
Credentials
DefaultCredentialsProvider everywhere, backed by an instance profile / task role / Lambda execution role. The course shows the resolution order once (Day 1) and then never revisits it, because nothing else is ever needed.
4. Spring Boot specifics the course actually uses
| Feature | Introduced | Why |
|---|---|---|
Constructor injection, @ConfigurationProperties | D3 | Typed config for bucket names, queue URLs, table names |
Profiles (local, dev, prod) | D3 | LocalStack/Testcontainers locally vs real AWS |
Actuator health + custom HealthIndicator | D4 | ALB/ECS health checks need a real readiness signal, not / |
| Graceful shutdown | D4 | Deregistration delay only helps if the app stops accepting and drains |
@Async / TaskExecutor | D5 | Concurrency in a request |
| Flyway | D6 | Live-service migrations, expand/contract |
| Spring Data JPA + HikariCP tuning | D6 | Pool sizing against instance connection limits |
| Spring Data Redis | D7 | Cache-aside, @Cacheable and why to often not use it |
@SqsListener (Spring Cloud AWS) and a hand-rolled poller | D8 | The annotation is convenient; the hand-rolled version is how you learn visibility timeouts |
| Spring Security resource server (JWT) | D11 | Validating a Cognito/OIDC token |
| Micrometer + CloudWatch registry | D13 | Application metrics that reach CloudWatch |
| Testcontainers + LocalStack | D8 | Integration tests without an AWS bill (with its fidelity limits stated) |
5. Testing strategy
The course is explicit that "it worked in the Console" is not a test.
| Level | Tool | Used for |
|---|---|---|
| Unit | JUnit 5 + Mockito | Business logic, idempotency decisions |
| SDK-level | aws-sdk-java-v2 test utilities / stubbed clients | Error-handling branches (throttle, conditional-check-failed) |
| Integration | Testcontainers + LocalStack | SQS, S3, DynamoDB flows end to end, in CI, free |
| Integration (real) | A dedicated dev account | Anything LocalStack models imperfectly — IAM evaluation, failover behaviour, ALB semantics |
| Load | k6 or hey | Throttling, autoscaling, backlog behaviour in Labs 7, 11, 15 |
Fidelity warning taught once, on Day 8: LocalStack does not reproduce IAM policy evaluation, throttling behaviour, or failover timing faithfully. Anything the course asks you to prove about those happens against real AWS.
6. Local development story
flowchart LR
subgraph LOCAL["Local"]
APP[Spring Boot<br/>profile=local] --> LS[(LocalStack:<br/>S3, SQS, DynamoDB)]
APP --> PG[(Postgres<br/>Testcontainer)]
end
subgraph AWS["AWS dev account"]
APP2[Same jar<br/>profile=dev] --> REAL[(Real services<br/>via task role)]
end
APP -.->|same code, different endpoint| APP2The only difference between profiles is an endpoint override and a credentials provider. The course shows this wiring once and reuses it.
7. Code quality bar
Every snippet in the course must be:
- Runnable — it compiles against the stated SDK/Spring versions; no
// ... rest of implementationin a sample presented as complete - Production-oriented — timeouts, retries, error handling and logging present, not stripped "for clarity"
- Commented where the why is non-obvious, not narrating the what
- Modern — Java 17 language features used naturally (records for DTOs, switch expressions, text blocks for policies/queries)
- Free of credentials — no access keys, no passwords, not even fake-looking ones
Snippets that are deliberately abbreviated are marked // abridged — full version in Lab N.