Guides/ Java

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

spring-bootspring-securityjpatestcontainers

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 testcontainers

What 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.java

The 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-security is only emitted for Spring Boot projects with Maven or Gradle.
  • --java-libraries flyway is kept only when --java-orm spring-data-jpa is 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 testcontainers adds 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 pointSecure Spring APIMinimal Spring JPA
SecurityHTTP Basic through Spring SecurityNot selected
MigrationsFlyway from the startAdd later
TestsJUnit 5 plus TestcontainersJUnit 5
Runtime visibilityActuator health/info/metricsBasic health endpoint
CostMore generated files and configSmaller 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/users

Flyway 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 build

Run 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/*.jar

Actuator 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

SymptomCheck
Every endpoint returns 401Send Basic auth credentials for everything except /health.
Flyway fails on startupConfirm the database is reachable and the migration history matches the schema.
Testcontainers tests fail locallyEnsure Docker is running and the current user can start containers.
App uses default admin/change-meSet APP_BASIC_USERNAME and APP_BASIC_PASSWORD in the runtime environment.
Actuator endpoint is missingConfirm 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