Learning/AWS Backend Developer/09 — Java & Spring Boot Integration Plan

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.

#IntegrationDayThe specific thing that makes it non-trivial
1STS — who am I1The credential provider chain, made visible
2S3 basic3Instance role credentials, no config
3CloudWatch logs + metrics3, 13Structured JSON, EMF vs PutMetricData, Micrometer
4Secrets Manager6Caching the secret, handling rotation without a restart
5Aurora / JDBC6HikariCP sizing, failover-aware config, JVM DNS TTL
6DynamoDB Enhanced Client7Bean mapping, GSI queries, conditional writes, TransactWriteItems
7ElastiCache / Redis7Spring Data Redis, serialization, TTL jitter, cluster-mode client config
8SQS8Long polling, batch receive, visibility heartbeats, manual vs Spring Cloud AWS listener
9SNS / EventBridge8, 9Event envelope design, PutEvents batching and partial failures
10S3 advanced10Transfer Manager, multipart, presigned URL generation and expiry
11Lambda handler5Static 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:

Java
@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:

CategoryExampleCorrect response
Throttling / transientProvisionedThroughputExceededException, RequestLimitExceeded, 5xxRetry with backoff — usually already handled by the SDK; know when it isn't
Business-conditionalConditionalCheckFailedExceptionNot an error — it's the answer. Often means "already processed."
Client errorAccessDeniedException, ValidationExceptionFail fast, alert, do not retry
Not foundNoSuchKeyExceptionDomain 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

FeatureIntroducedWhy
Constructor injection, @ConfigurationPropertiesD3Typed config for bucket names, queue URLs, table names
Profiles (local, dev, prod)D3LocalStack/Testcontainers locally vs real AWS
Actuator health + custom HealthIndicatorD4ALB/ECS health checks need a real readiness signal, not /
Graceful shutdownD4Deregistration delay only helps if the app stops accepting and drains
@Async / TaskExecutorD5Concurrency in a request
FlywayD6Live-service migrations, expand/contract
Spring Data JPA + HikariCP tuningD6Pool sizing against instance connection limits
Spring Data RedisD7Cache-aside, @Cacheable and why to often not use it
@SqsListener (Spring Cloud AWS) and a hand-rolled pollerD8The annotation is convenient; the hand-rolled version is how you learn visibility timeouts
Spring Security resource server (JWT)D11Validating a Cognito/OIDC token
Micrometer + CloudWatch registryD13Application metrics that reach CloudWatch
Testcontainers + LocalStackD8Integration 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.

LevelToolUsed for
UnitJUnit 5 + MockitoBusiness logic, idempotency decisions
SDK-levelaws-sdk-java-v2 test utilities / stubbed clientsError-handling branches (throttle, conditional-check-failed)
IntegrationTestcontainers + LocalStackSQS, S3, DynamoDB flows end to end, in CI, free
Integration (real)A dedicated dev accountAnything LocalStack models imperfectly — IAM evaluation, failover behaviour, ALB semantics
Loadk6 or heyThrottling, 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| APP2

The 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 implementation in 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.