Full Stack • Java • System Design • Cloud • AI Engineering

AWS Secrets Manager with Spring Boot

Learn how to integrate AWS Secrets Manager with Spring Boot step by step. This guide covers secure secret storage, database credentials, API keys, IAM roles, AWS SDK integration, secret rotation, caching, and production best practices.


Introduction

Every enterprise application needs sensitive configuration.

Examples:

  • Database username
  • Database password
  • API keys
  • OAuth client secret
  • JWT signing key
  • Encryption keys
  • Third-party credentials

A common mistake is storing secrets directly in:

application.yml

or

application.properties

or even inside source code.

This is risky because secrets may be exposed through:

  • GitHub repository
  • Build logs
  • Docker images
  • CI/CD pipelines
  • Developer machines
  • Application logs

AWS Secrets Manager helps store, retrieve, rotate, and audit secrets securely.

In this article, we will integrate Spring Boot with AWS Secrets Manager using the AWS SDK for Java 2.x.


Learning Objectives

After completing this article, you will understand:

  • What is AWS Secrets Manager?
  • Why not hardcode secrets?
  • How Secrets Manager works
  • How Spring Boot reads secrets securely
  • How to create a secret
  • How to configure IAM permissions
  • How to use AWS SDK for Java
  • How to load database credentials
  • How to expose a test API
  • How to cache secrets
  • How to rotate secrets
  • Production best practices

What is AWS Secrets Manager?

AWS Secrets Manager is a managed AWS service used to store and retrieve secrets securely.

It supports:

  • Secure secret storage
  • Encryption using AWS KMS
  • IAM-based access control
  • Secret versioning
  • Automatic rotation
  • CloudTrail audit logs
  • API-based retrieval

Why Use Secrets Manager?

Without Secrets Manager:

Spring Boot
  |
  |-- application.yml
      |-- username
      |-- password
      |-- api-key

Problems:

  • Secrets are visible in code
  • Difficult to rotate
  • Risk of accidental commit
  • Weak auditability
  • Manual credential management

With Secrets Manager:

Spring Boot
  |
  |-- IAM Role
  |
  |-- Secrets Manager
      |-- encrypted secret

Benefits:

  • No hardcoded secrets
  • Centralized management
  • Encryption at rest
  • IAM access control
  • Rotation support
  • Better compliance

High-Level Architecture

flowchart LR
    APP[Spring Boot Application]
    ROLE[IAM Role]
    SM[AWS Secrets Manager]
    KMS[AWS KMS]
    DB[(Amazon RDS / Aurora)]

    APP --> ROLE
    ROLE --> SM
    SM --> KMS
    APP --> DB

Production Architecture

flowchart TD
    USERS[Users]
    ALB[Application Load Balancer]
    APP[Spring Boot Service]
    ROLE[IAM Role]
    SM[AWS Secrets Manager]
    RDS[(Amazon RDS / Aurora)]
    CW[CloudWatch Logs]
    CT[CloudTrail]

    USERS --> ALB
    ALB --> APP
    APP --> ROLE
    ROLE --> SM
    APP --> RDS
    APP --> CW
    SM --> CT

Secrets Manager Use Cases

Common use cases:

  • Store RDS credentials
  • Store Aurora credentials
  • Store API keys
  • Store OAuth client secrets
  • Store JWT secret keys
  • Store encryption keys
  • Store third-party service credentials
  • Rotate database passwords automatically

Secrets Manager vs Parameter Store

Feature Secrets Manager SSM Parameter Store
Secret Rotation Built-in Manual/custom
Cost Paid Standard tier free
Best For Passwords, credentials Config values
Encryption KMS KMS optional
Versioning Yes Yes
RDS Rotation Built-in templates Custom

Use Secrets Manager for sensitive credentials.

Use Parameter Store for non-sensitive configuration or lower-cost config storage.


Step 1: Create a Secret

Go to:

AWS Console
→ Secrets Manager
→ Store a new secret

Choose:

Other type of secret

Add key-value pairs:

{
  "username": "postgres",
  "password": "MyStrongPassword123",
  "host": "codewithvenu-db.cluster-abc123.us-east-1.rds.amazonaws.com",
  "port": "5432",
  "dbname": "appdb"
}

Secret name:

codewithvenu/dev/rds

Step 2: Create Secret Using AWS CLI

aws secretsmanager create-secret \
  --name codewithvenu/dev/rds \
  --description "RDS credentials for CodeWithVenu dev environment" \
  --secret-string '{
    "username":"postgres",
    "password":"MyStrongPassword123",
    "host":"codewithvenu-db.cluster-abc123.us-east-1.rds.amazonaws.com",
    "port":"5432",
    "dbname":"appdb"
  }'

Expected output:

{
  "ARN": "arn:aws:secretsmanager:us-east-1:123456789012:secret:codewithvenu/dev/rds-AbCdEf",
  "Name": "codewithvenu/dev/rds"
}

Step 3: Verify Secret

aws secretsmanager get-secret-value \
  --secret-id codewithvenu/dev/rds

Expected output:

{
  "Name": "codewithvenu/dev/rds",
  "SecretString": "{\"username\":\"postgres\",\"password\":\"MyStrongPassword123\",\"host\":\"codewithvenu-db.cluster-abc123.us-east-1.rds.amazonaws.com\",\"port\":\"5432\",\"dbname\":\"appdb\"}"
}

Step 4: IAM Policy for Spring Boot

Spring Boot should have permission to read only the required secret.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadSpecificSecret",
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue"
      ],
      "Resource": [
        "arn:aws:secretsmanager:us-east-1:123456789012:secret:codewithvenu/dev/rds-*"
      ]
    }
  ]
}

Attach this policy to:

  • EC2 Instance Role
  • ECS Task Role
  • EKS IRSA Role
  • Lambda Execution Role

Do not attach broad permissions like:

secretsmanager:*

unless required for admin tools.


Step 5: Spring Boot Project Structure

springboot-secrets-manager-demo
 ┣ src/main/java/com/codewithvenu/secrets
 ┃ ┣ SecretsManagerApplication.java
 ┃ ┣ config
 ┃ ┃ ┗ AwsSecretsConfig.java
 ┃ ┣ controller
 ┃ ┃ ┗ SecretTestController.java
 ┃ ┣ dto
 ┃ ┃ ┗ DatabaseSecret.java
 ┃ ┗ service
 ┃   ┗ SecretsManagerService.java
 ┣ src/main/resources
 ┃ ┗ application.yml
 ┗ pom.xml

Step 6: Maven Dependencies

<dependencies>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>secretsmanager</artifactId>
        <version>2.25.60</version>
    </dependency>

    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>auth</artifactId>
        <version>2.25.60</version>
    </dependency>

    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>regions</artifactId>
        <version>2.25.60</version>
    </dependency>

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>

</dependencies>

Step 7: Configure application.yml

server:
  port: 8080

spring:
  application:
    name: springboot-secrets-manager-demo

aws:
  region: us-east-1
  secrets-manager:
    rds-secret-name: codewithvenu/dev/rds

Do not put database password directly here.

Wrong:

spring:
  datasource:
    password: MyStrongPassword123

Correct:

aws:
  secrets-manager:
    rds-secret-name: codewithvenu/dev/rds

Step 8: Configure Secrets Manager Client

Create:

src/main/java/com/codewithvenu/secrets/config/AwsSecretsConfig.java
package com.codewithvenu.secrets.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;

@Configuration
public class AwsSecretsConfig {

    @Value("${aws.region}")
    private String awsRegion;

    @Bean
    public SecretsManagerClient secretsManagerClient() {
        return SecretsManagerClient.builder()
                .region(Region.of(awsRegion))
                .credentialsProvider(DefaultCredentialsProvider.create())
                .build();
    }
}

Step 9: Create DTO for Secret

Create:

src/main/java/com/codewithvenu/secrets/dto/DatabaseSecret.java
package com.codewithvenu.secrets.dto;

public class DatabaseSecret {

    private String username;
    private String password;
    private String host;
    private String port;
    private String dbname;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public String getPort() {
        return port;
    }

    public void setPort(String port) {
        this.port = port;
    }

    public String getDbname() {
        return dbname;
    }

    public void setDbname(String dbname) {
        this.dbname = dbname;
    }
}

Step 10: Create Secrets Manager Service

Create:

src/main/java/com/codewithvenu/secrets/service/SecretsManagerService.java
package com.codewithvenu.secrets.service;

import com.codewithvenu.secrets.dto.DatabaseSecret;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;

@Service
public class SecretsManagerService {

    private final SecretsManagerClient secretsManagerClient;
    private final ObjectMapper objectMapper;

    @Value("${aws.secrets-manager.rds-secret-name}")
    private String rdsSecretName;

    public SecretsManagerService(
            SecretsManagerClient secretsManagerClient,
            ObjectMapper objectMapper
    ) {
        this.secretsManagerClient = secretsManagerClient;
        this.objectMapper = objectMapper;
    }

    public DatabaseSecret getDatabaseSecret() {
        try {
            GetSecretValueRequest request = GetSecretValueRequest.builder()
                    .secretId(rdsSecretName)
                    .build();

            GetSecretValueResponse response = secretsManagerClient.getSecretValue(request);

            String secretString = response.secretString();

            return objectMapper.readValue(secretString, DatabaseSecret.class);

        } catch (Exception ex) {
            throw new RuntimeException("Failed to retrieve database secret from AWS Secrets Manager", ex);
        }
    }
}

Step 11: Create Test Controller

Create:

src/main/java/com/codewithvenu/secrets/controller/SecretTestController.java
package com.codewithvenu.secrets.controller;

import com.codewithvenu.secrets.dto.DatabaseSecret;
import com.codewithvenu.secrets.service.SecretsManagerService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping("/api/secrets")
public class SecretTestController {

    private final SecretsManagerService secretsManagerService;

    public SecretTestController(SecretsManagerService secretsManagerService) {
        this.secretsManagerService = secretsManagerService;
    }

    @GetMapping("/database")
    public ResponseEntity<Map<String, Object>> getDatabaseSecret() {

        DatabaseSecret secret = secretsManagerService.getDatabaseSecret();

        return ResponseEntity.ok(Map.of(
                "username", secret.getUsername(),
                "host", secret.getHost(),
                "port", secret.getPort(),
                "dbname", secret.getDbname(),
                "password", "********"
        ));
    }
}

Important:

Never return real passwords in API responses.

This endpoint masks the password only for learning.


Step 12: Main Application Class

package com.codewithvenu.secrets;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SecretsManagerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SecretsManagerApplication.class, args);
    }
}

Step 13: Run Application

mvn spring-boot:run

Expected output:

Tomcat started on port 8080
Started SecretsManagerApplication

Test:

curl http://localhost:8080/api/secrets/database

Expected output:

{
  "username": "postgres",
  "host": "codewithvenu-db.cluster-abc123.us-east-1.rds.amazonaws.com",
  "port": "5432",
  "dbname": "appdb",
  "password": "********"
}

Step 14: Build JDBC URL Dynamically

You can build a JDBC URL using the retrieved secret.

public String buildJdbcUrl(DatabaseSecret secret) {
    return "jdbc:postgresql://" +
            secret.getHost() +
            ":" +
            secret.getPort() +
            "/" +
            secret.getDbname();
}

Output:

jdbc:postgresql://codewithvenu-db.cluster-abc123.us-east-1.rds.amazonaws.com:5432/appdb

Secrets Retrieval Flow

flowchart LR
    APP[Spring Boot]
    SDK[AWS SDK]
    IAM[IAM Role]
    SM[Secrets Manager]
    KMS[AWS KMS]
    SECRET[Database Secret]

    APP --> SDK
    SDK --> IAM
    IAM --> SM
    SM --> KMS
    SM --> SECRET

Step 15: Use Secrets for DataSource

In production, one option is to create a custom DataSource bean.

@Configuration
public class DataSourceConfig {

    @Bean
    public DataSource dataSource(SecretsManagerService secretsManagerService) {

        DatabaseSecret secret = secretsManagerService.getDatabaseSecret();

        HikariConfig config = new HikariConfig();

        config.setJdbcUrl("jdbc:postgresql://" +
                secret.getHost() + ":" +
                secret.getPort() + "/" +
                secret.getDbname());

        config.setUsername(secret.getUsername());
        config.setPassword(secret.getPassword());
        config.setMaximumPoolSize(10);

        return new HikariDataSource(config);
    }
}

Required dependencies:

<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
</dependency>

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>

Step 16: Secret Caching

Calling Secrets Manager on every request is not recommended.

Better approach:

Application Startup
  ↓
Load Secret Once
  ↓
Cache in Memory
  ↓
Reuse for Database Connection

For frequently accessed secrets:

  • Cache secrets in memory
  • Refresh periodically
  • Avoid calling Secrets Manager per request

Secret Caching Flow

flowchart LR
    APP_START[Application Startup]
    LOAD[Load Secret]
    CACHE[In-Memory Cache]
    REQUEST[Application Request]

    APP_START --> LOAD
    LOAD --> CACHE
    REQUEST --> CACHE

Step 17: Secret Rotation

Secrets Manager supports rotation.

Common rotation examples:

  • RDS password rotation
  • Aurora password rotation
  • API key rotation
  • OAuth secret rotation

Rotation flow:

flowchart TD
    OLD[Old Secret]
    ROTATE[Rotation Lambda]
    NEW[New Secret]
    APP[Spring Boot Application]

    OLD --> ROTATE
    ROTATE --> NEW
    APP --> NEW

For database credentials, AWS provides built-in rotation templates for supported engines.


Step 18: Local Development Options

For local development, you can use:

aws configure

or environment variables:

export AWS_PROFILE=dev
export AWS_REGION=us-east-1

Run:

mvn spring-boot:run

The AWS SDK uses your local AWS profile.


Step 19: Production Deployment Options

Platform Recommended Credential Source
EC2 Instance Profile
ECS Task Role
EKS IAM Role for Service Account
Lambda Execution Role
Local AWS Profile

Never use static keys in production.


Step 20: Common Errors and Fixes

Error 1: AccessDeniedException

Cause:

IAM role does not have secretsmanager:GetSecretValue permission.

Fix:

Attach least-privilege IAM policy.


Error 2: ResourceNotFoundException

Cause:

Secret name is wrong or secret exists in a different region.

Fix:

Check:

aws secretsmanager list-secrets --region us-east-1

Error 3: KMS Access Denied

Cause:

Secret uses a custom KMS key and the IAM role lacks decrypt permission.

Fix:

Add:

kms:Decrypt

for the KMS key.


Error 4: Timeout

Cause:

Application runs in private subnet without NAT Gateway or VPC Endpoint.

Fix:

Create an Interface VPC Endpoint for Secrets Manager.


VPC Endpoint Architecture

flowchart LR
    APP[Spring Boot Private Subnet]
    VPCE[VPC Interface Endpoint]
    SM[AWS Secrets Manager]

    APP --> VPCE
    VPCE --> SM

Step 21: Security Best Practices

  • Never hardcode secrets
  • Never commit secrets to GitHub
  • Never print secrets in logs
  • Use IAM roles
  • Use least privilege
  • Use Secrets Manager for credentials
  • Use Parameter Store for general config
  • Enable CloudTrail
  • Enable secret rotation where possible
  • Use KMS encryption
  • Use VPC endpoints for private access
  • Cache secrets safely
  • Separate secrets by environment

Example names:

codewithvenu/dev/rds
codewithvenu/test/rds
codewithvenu/prod/rds

Production Architecture

flowchart TD
    USERS[Users]
    ROUTE53[Route 53]
    ALB[Application Load Balancer]
    APP1[Spring Boot App AZ1]
    APP2[Spring Boot App AZ2]
    SM[AWS Secrets Manager]
    RDS[(Amazon Aurora)]
    VPCE[VPC Endpoint]
    CW[CloudWatch]
    CT[CloudTrail]

    USERS --> ROUTE53
    ROUTE53 --> ALB
    ALB --> APP1
    ALB --> APP2

    APP1 --> VPCE
    APP2 --> VPCE
    VPCE --> SM

    APP1 --> RDS
    APP2 --> RDS

    APP1 --> CW
    APP2 --> CW
    SM --> CT

Interview Questions

What is AWS Secrets Manager?

AWS Secrets Manager is a managed AWS service used to securely store, retrieve, rotate, and audit secrets such as database passwords, API keys, and OAuth credentials.


Why should Spring Boot use Secrets Manager?

It avoids hardcoding sensitive values in application configuration or source code and enables secure access through IAM roles.


What IAM permission is needed to read a secret?

secretsmanager:GetSecretValue

If a custom KMS key is used, the application may also need:

kms:Decrypt

Should secrets be fetched on every API call?

No. Secrets should usually be fetched at application startup and cached safely.


How does Spring Boot authenticate to Secrets Manager in AWS?

Spring Boot uses AWS SDK default credentials provider chain, which retrieves temporary credentials from EC2 Instance Profile, ECS Task Role, EKS IRSA, or Lambda Execution Role.


What is secret rotation?

Secret rotation is the process of automatically changing secret values, such as database passwords, using AWS Secrets Manager and optionally a Lambda rotation function.


Summary

In this article, we learned how to integrate AWS Secrets Manager with Spring Boot.

We covered:

  • Secret management fundamentals
  • Secret creation using AWS Console and CLI
  • IAM policy for secret access
  • AWS SDK configuration
  • Reading secrets from Spring Boot
  • Masking sensitive values
  • Building database configuration dynamically
  • Secret caching
  • Secret rotation
  • VPC Endpoint usage
  • Production best practices
  • Interview questions

AWS Secrets Manager is one of the most important services for securing enterprise Spring Boot applications. It helps eliminate hardcoded credentials, improves auditability, enables rotation, and integrates cleanly with IAM roles.


Loading likes...

Comments

Share a question, correction, or practical insight about this article.

Loading approved comments...