Secure Spring Boot API
Create a secure Java API with Spring Boot, Spring Security, Spring Data JPA, Flyway, Gradle, JUnit 5, and Testcontainers.
Updated 2026-05-12
Use this stack when you want a Spring Boot backend with persistence, security, database migrations, and integration-test tooling.
npm create better-fullstack@latest my-secure-spring-api -- \
--ecosystem java \
--java-web-framework spring-boot \
--java-build-tool gradle \
--database postgres \
--java-orm spring-data-jpa \
--java-auth spring-security \
--java-libraries spring-actuator flyway \
--java-testing-libraries junit5 testcontainersWhat this creates
- A Spring Boot Java API.
- Gradle as the build tool.
- Spring Data JPA.
- Spring Security.
- Spring Actuator and Flyway.
- JUnit 5 and Testcontainers.
Generated shape
This stack starts closer to a production API baseline. It includes Gradle wrapper files, Spring Security configuration, JPA sample endpoints, Flyway migration files, Actuator endpoints, and a Testcontainers test path.
my-secure-spring-api/
├── gradlew
├── gradle/
├── build.gradle.kts
├── settings.gradle.kts
├── src/main/java/com/example/mysecurespringapi/
│ ├── Application.java
│ ├── config/
│ │ └── SecurityConfig.java
│ ├── controller/
│ │ ├── HealthController.java
│ │ └── UserController.java
│ ├── domain/
│ │ └── AppUser.java
│ ├── repository/
│ │ └── AppUserRepository.java
│ └── service/
│ └── AppUserService.java
├── src/main/resources/
│ ├── application.yml
│ └── db/migration/V1__init.sql
└── src/test/java/com/example/mysecurespringapi/
├── ApplicationTests.java
└── ApplicationContainerTests.javaThe generated security path uses HTTP Basic with an in-memory user. It is a practical secure default for local and internal API work, not a complete product authentication system.
Compatibility notes
Spring Security, JPA, and Spring libraries require Spring Boot with a Java build tool. Flyway is paired with the Spring Data JPA path in Better Fullstack.
Additional compatibility details:
--java-auth spring-securityis only emitted for Spring Boot projects with Maven or Gradle.--java-libraries flywayis kept only when--java-orm spring-data-jpais selected.- If both Flyway and Liquibase are selected in a broader stack, the generator keeps Flyway and drops Liquibase for this path.
--java-testing-libraries testcontainersadds container-based integration test support, so local Docker availability matters for that test lane.
When to choose it
Choose this for production-oriented Java APIs where security, migrations, and integration tests should be present early.
Choose the minimal Spring guide when you want the smallest JPA example and plan to add security later. Choose this guide when the first commit should already answer "how are endpoints protected, how do schemas change, and how do we test against a real database?"
| Decision point | Secure Spring API | Minimal Spring JPA |
|---|---|---|
| Security | HTTP Basic through Spring Security | Not selected |
| Migrations | Flyway from the start | Add later |
| Tests | JUnit 5 plus Testcontainers | JUnit 5 |
| Runtime visibility | Actuator health/info/metrics | Basic health endpoint |
| Cost | More generated files and config | Smaller starting point |
Representative snippets
The generated security config permits /health and protects the rest of the application with HTTP Basic.
@Configuration
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf((csrf) -> csrf.disable())
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/health").permitAll()
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
}Credentials are read from environment-backed properties with local defaults.
@Bean
UserDetailsService userDetailsService(
@Value("${APP_BASIC_USERNAME:admin}") String username,
@Value("${APP_BASIC_PASSWORD:change-me}") String password,
PasswordEncoder passwordEncoder
) {
UserDetails user = User.withUsername(username)
.password(passwordEncoder.encode(password))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}Use the protected endpoints with Basic auth while developing:
curl -u "$APP_BASIC_USERNAME:$APP_BASIC_PASSWORD" \
http://localhost:8080/usersFlyway owns schema creation when selected:
CREATE TABLE IF NOT EXISTS app_users (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
display_name VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL
);Migrations and tests
Flyway changes the Hibernate posture: generated configuration validates the schema rather than letting Hibernate freely update it. Add a new V2__...sql file for each database change and keep migrations reviewable.
./gradlew test
./gradlew bootRun
./gradlew buildRun the Testcontainers lane when Docker is available. If Docker is unavailable in your local environment, keep unit tests separate from container tests so the fast lane remains usable.
@SpringBootTest
class ApplicationContainerTests {
@Test
void contextLoadsAgainstContainerDatabase() {
}
}Deployment notes
Before deploying, replace local credentials, add the PostgreSQL JDBC driver if your generated build still only includes H2, configure a managed PostgreSQL datasource, and run Flyway migrations as part of startup or release orchestration.
export APP_BASIC_USERNAME="service-admin"
export APP_BASIC_PASSWORD="replace-with-secret"
export SPRING_DATASOURCE_URL="jdbc:postgresql://db.example.com:5432/my_secure_spring_api"
export SPRING_DATASOURCE_DRIVER_CLASS_NAME="org.postgresql.Driver"
export SPRING_DATASOURCE_USERNAME="app"
export SPRING_DATASOURCE_PASSWORD="replace-with-secret"
./gradlew build
java -jar build/libs/*.jarActuator is selected through --java-libraries spring-actuator, so health and metrics endpoints are available for platform probes. Review endpoint exposure before putting the service on the public internet.
Troubleshooting
| Symptom | Check |
|---|---|
| Every endpoint returns 401 | Send Basic auth credentials for everything except /health. |
| Flyway fails on startup | Confirm the database is reachable and the migration history matches the schema. |
| Testcontainers tests fail locally | Ensure Docker is running and the current user can start containers. |
App uses default admin/change-me | Set APP_BASIC_USERNAME and APP_BASIC_PASSWORD in the runtime environment. |
| Actuator endpoint is missing | Confirm spring-actuator is still present in --java-libraries. |
Tradeoffs
This is heavier than a minimal Spring Boot API. If you want a smaller starting point, use Spring Boot with PostgreSQL and JPA or remove optional libraries in the builder.
Next steps
- Open the Stack Builder.
- Read the Java ecosystem docs.