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.
Comments
Share a question, correction, or practical insight about this article.
Checking login status...
Loading approved comments...