Security Tools
Published on: April 30, 2026
10 min read

How to Secure a Spring Boot Application in 10 Minutes: The 2026 Developer Security Checklist

✍️ By Priya Singh (Principal Software Engineer)

Principal Software Engineer

How to Secure a Spring Boot Application in 10 Minutes: The 2026 Developer Security Checklist
By Priya SinghSenior Technical Insights
Try the Tool

Ready to put this into practice?

We've built a high-performance REST API Tester specifically for the topics discussed in this article. It's free, secure, and runs entirely in your browser.

Spring Boot is not secure by default. Adding spring-boot-starter-security to your pom.xml is the beginning of the work, not the end of it. Without deliberate configuration, Actuator endpoints expose internal state publicly, the Security Filter Chain may permit unauthenticated access to routes you assumed were protected, CORS defaults allow all origins, and secrets end up in application.properties in plaintext. This checklist covers the 10 security checks I run on every Spring Boot application before it goes to production — each one takes under a minute, and every one of them catches real issues in real systems.

Every week I look at Spring Boot applications built by experienced engineers at companies that take security seriously. The same misconfigurations appear. Not because the engineers are careless — but because Spring Boot's defaults optimise for development convenience, not production hardening. What ships to production is often what was configured in the first sprint and never revisited.

This is not a tutorial. It is a production audit. Here is what I check, what I find, and what you should fix.

Over the last two years, the Spring Boot security issues I reviewed in client projects were rarely exotic zero-day exploits or framework-level bugs. They were almost always ordinary production oversights: an exposed /actuator/env , a JWT filter wired incorrectly, a native query accepting raw input, a Git repository containing plaintext database credentials, or a dependency scanner that nobody ever configured. None of these looked urgent during development. Every one of them became urgent after deployment.

Spring Boot Security Reality in 2026

A06:2021

Vulnerable & Outdated Components is still in the OWASP Top 10 — most Spring apps ship with dependency drift

Thousands monthly

Public GitHub repositories still expose application.properties secrets — hardcoded credentials remain common

Pentest reports

Misconfigured Actuator and CORS endpoints continue appearing in enterprise penetration testing reports — defaults plus assumptions create exposure

The 10-Minute Spring Boot Security Audit Dashboard

Before the detail: here is the full audit at a glance. Use this table as your quick reference. The checks below explain each line in depth.

SPRING BOOT SECURITY AUDIT — 10 CHECKS AT A GLANCE # CHECK SEVERITY MOST COMMON FAILURE 1 Spring Boot + Security version check CRITICAL Outdated with known CVEs 2 Actuator endpoint exposure CRITICAL /env and /heapdump on port 8080 3 SecurityFilterChain route coverage CRITICAL anyRequest().permitAll() as default 4 Hardcoded secrets in config files CRITICAL JWT secret or DB password in .properties 5 SQL injection in repos and queries CRITICAL + concatenation in native queries 6 Input validation on DTOs and params HIGH No @Valid on @RequestBody 7 JWT and session misconfiguration HIGH HS256 with weak secret, 24h expiry 8 CORS misconfiguration HIGH allowedOrigins("*") in production 9 Security headers and error leakage MEDIUM Stack traces in error responses 10 Dependency and container CVE scan MEDIUM No OWASP Dependency-Check in CI CRITICAL HIGH MEDIUM
The 10-check Spring Boot security audit — severity ratings and most common failure for each.
Spring Boot's auto-configuration makes development faster by choosing sensible defaults — but "sensible for development" is not the same as "safe for production." Source: OWASP Top 10 · owasp.org .

Check #1 — Patch Spring Boot and Spring Security Versions First

This is not glamorous. Teams skip it because it feels like maintenance, not security. But outdated Spring Boot and Spring Security versions are the most common source of exploitable CVEs in Java applications I review. A vulnerability in your framework version affects every endpoint, every request, every user — without a single line of your own code being wrong.

pom.xml · check and pin your Spring Boot parent version explicitly

<parent>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-parent
</artifactId>
<version>3.3.5
</version>
<!-- pin explicitly — never use LATEST or RELEASE -->
</parent>
<!-- Spring Security version — override if transitive resolution pulls older -->
<properties>
<spring-security.version>6.3.4
</spring-security.version>
</properties>
Shell · check what versions are actually on the classpath

# Maven — print effective dependency tree filtered to security libs
mvn dependency:tree | grep -E "spring-boot|spring-security|tomcat|jackson"
# Gradle
./gradlew dependencies | grep -E "spring-boot|spring-security"
🚨
What I find

Teams running Spring Boot 2.x in 2026 without a migration plan. Spring Boot 2.x reached end-of-life in November 2023. Every month it stays in production accumulates unpatched CVEs. The effort to stay on 2.x and patch vulnerabilities manually exceeds the effort to migrate to 3.x.

Check #2 — Lock Down Every /actuator/** Endpoint Before Production

Spring Boot Actuator is incredibly useful in development. In production, without explicit lockdown, it hands attackers a complete map of your application: environment variables including secrets, full heap dumps, all bean definitions, all URL mappings, and your running configuration. I find publicly accessible Actuator endpoints in production more often than I find properly locked-down ones.

application.yml · production-safe Actuator configuration

management:
# Only expose health and info — nothing else
endpoints:
web:
exposure:
include: health, info
endpoint:
health:
show-details: never       # never expose DB status or disk space to public
info:
enabled: true
# Run Actuator on a separate internal port — never expose 8081 publicly
server:
port: 8081
SecurityFilterChain · lock Actuator routes even on the internal port

http.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health", "/actuator/info").permitAll()
.requestMatchers("/actuator/**").hasRole("ADMIN")   // everything else needs ADMIN
.anyRequest().authenticated()
);
⚠️
What I find in production

/actuator/env returning database passwords. /actuator/heapdump accessible with a single GET request — a heap dump contains session tokens, in-memory secrets, and user data in plain text. /actuator/mappings giving attackers a complete list of all routes to probe.

🚨
"Only internal users know the URL" is not access control

In multiple production audits, /actuator/env and /actuator/heapdump were the two endpoints teams forgot to disable because they assumed obscurity was protection. Internal URLs leak faster than teams expect — through application logs, browser history, developer screenshots, reverse proxy access logs, shared Postman collections, and internal documentation. Restrict by configuration, not by assumption.

Check #3 — Verify the Spring Security Filter Chain Actually Protects All Routes

This is the check that makes engineers uncomfortable when I raise it — because they assumed Spring Security was protecting everything. The assumption is wrong. The default Spring Security configuration in 3.x requires all requests to be authenticated, which sounds correct. But the moment you write a custom SecurityFilterChain bean, you override that default. Your custom chain protects exactly what you tell it to protect. Everything else may fall through.

Java · SecurityFilterChain — the difference between safe and broken

// DANGEROUS — anyRequest().permitAll() at the end
// This means: if a route is not explicitly listed above, allow anyone in.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().permitAll()   // ← EVERY unlisted route is publicly accessible
);
return http.build();
}
// CORRECT — anyRequest().authenticated() as the catch-all
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/actuator/health", "/actuator/info").permitAll()
.requestMatchers("/actuator/**").hasRole("ADMIN")
.anyRequest().authenticated()  // ← default: must be authenticated
);
return http.build();
}
🚨
Where this goes wrong

A developer adds .anyRequest().permitAll() to stop a test failing during development. The PR merges. The admin endpoints are now publicly accessible. I have found /api/admin/users returning full user lists on production systems because of exactly this pattern.

The Most Dangerous Spring Boot Security Mistake: Assuming the Starter Did It For You

One of the most repeated phrases I hear from teams during internal reviews is: "We already added Spring Security."

The dependency being present does not mean the application is secure. I still regularly see APIs where:

  • custom filters are registered in the wrong order in the filter chain,
  • /api/admin/** is accidentally excluded from the matcher pattern,
  • OPTIONS preflight requests bypass authentication unexpectedly,
  • Swagger UI, H2 console, and internal admin routes remain publicly reachable,
  • or legacy controllers sit outside the intended request matcher pattern entirely.

Spring Boot security failures are rarely missing libraries. They are incorrect assumptions about what the library is actually protecting.

Check #4 — Remove Hardcoded Secrets and Plain Credentials from Configuration Files

Hardcoded secrets in application.properties or application.yml committed to version control is the most common, most preventable, and most damaging misconfiguration I encounter. Once a secret is in git history, it is compromised permanently — even if you delete the file later. Git history is forever, and GitHub secret scanners, Gitleaks, and attacker tooling are specifically designed to find these.

BAD — secrets hardcoded in application.properties

spring.datasource.password=MyProd$ecret123
jwt.secret=supersecretjwtkey
spring.security.oauth2.client.registration.google.client-secret=GOCSPX-abc123
GOOD — secrets from environment variables or a secrets manager

# application.yml — reference env vars, never inline values
spring:
datasource:
password: ${DB_PASSWORD}           # injected at runtime from env or vault
jwt:
secret: ${JWT_SECRET}
spring:
security:
oauth2:
client:
registration:
google:
client-secret: ${GOOGLE_CLIENT_SECRET}
# For production: use AWS Secrets Manager, HashiCorp Vault, or Azure Key Vault
# Spring Cloud Vault integrates directly with Spring Boot's property loading
Shell · scan for hardcoded secrets before committing

# Gitleaks — scans current repo for leaked secrets
gitleaks detect --source=. --verbose
# Trufflehog — scans git history, not just current state
trufflehog git file://. --only-verified
If a secret is already in git history

Rotate it immediately — the secret is already compromised. Then use git filter-branch or BFG Repo Cleaner to remove it from history. Add application-*.properties with actual values to .gitignore . Use application-local.properties for development only, never committed.

🚨
How secrets are actually discovered

Hardcoded secrets are rarely discovered by attackers through your running application first. They are discovered through source control leaks, CI build logs, Docker image layer inspection, shared config archives, and copied staging environment files. Once a credential is committed to git — even in a private repository — assume it is eventually recoverable by anyone who gains repository access, now or in the future.

Check #5 — Run a 60-Second SQL Injection Audit Across Repositories and Native Queries

Spring Data JPA repository derived methods are safe. The moment you write a custom @Query with string concatenation, or build JPQL dynamically, or use native queries without named parameters — you have introduced SQL injection. I cover this in detail in our SQL Injection in Spring Boot guide . For the audit, here is the 60-second check.

Shell · grep audit — find string concatenation in query construction

# Find any + operator near query-related strings in your codebase
grep -rn "createQuery\|createNativeQuery\|nativeQuery" src/ | grep '".*+\|+.*"'
# Find @Query annotations with string interpolation (should return nothing safe)
grep -rn '@Query' src/ | grep '".*\+\|+.*"'
# Run Semgrep java.sql.injection family against entire src directory
semgrep --config "p/java" --include="*.java" src/
💡
Quick fix for the most common pattern

Every @Query(nativeQuery=true) should use :namedParam binding, not string concatenation. Every EntityManager.createQuery() call should use .setParameter("name", value) . Add Semgrep java.sql.injection rules to your CI pipeline — it catches these on every PR automatically.

🗄️

When reviewing complex queries visually, a browser-based SQL formatter (such as LearnHubly's SQL Formatter ) makes concatenation risks immediately visible — a + operator adjacent to a variable stands out clearly in a formatted query.

Check #6 — Validate Every Request Parameter, DTO, and User Input Before Business Logic

Bean Validation (JSR 380) exists in every Spring Boot project via spring-boot-starter-validation . Developers add it, annotate a few fields, and then forget to add @Valid on the controller parameter — which means the annotations run, but their failures are silently ignored. The data enters business logic unchecked.

Java · common validation misconfiguration — @Valid missing from controller

// BAD — @Valid is missing. All @NotBlank, @Email, @Size annotations
// on CreateUserRequest are completely ignored. No exception thrown.
@PostMapping("/users")
public ResponseEntity
<User> createUser(@RequestBody CreateUserRequest request) {
return ResponseEntity.ok(userService.create(request));
}
// GOOD — @Valid triggers constraint validation before method body executes
@PostMapping("/users")
public ResponseEntity
<User> createUser(@RequestBody @Valid CreateUserRequest request) {
return ResponseEntity.ok(userService.create(request));
}
// The DTO
public record CreateUserRequest(
@NotBlank(message = "Name is required")
@Size(max = 100)
String name,
@Email(message = "Valid email required")
@NotBlank
String email,
@Pattern(regexp = "^(?=.*[A-Z])(?=.*\\d).{8,}$",
message = "Password must be 8+ chars with uppercase and digit")
String password
) {}
Java · global exception handler for validation failures

@RestControllerAdvice
public class ValidationExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity
<Map
 handleValidation(
MethodArgumentNotValidException ex) {
Map
<String, String> errors = ex.getBindingResult()
.getFieldErrors().stream()
.collect(Collectors.toMap(
FieldError::getField,
FieldError::getDefaultMessage
));
return ResponseEntity.badRequest().body(errors);
// Returns: {"email": "Valid email required", "name": "Name is required"}
// Never expose internal exception details — just field + message
}
}

Check #7 — Review JWT, Session, and Authentication Misconfigurations

JWT misconfiguration in Spring Boot applications takes three consistent forms: using HS256 with a weak or hardcoded secret, setting access token expiry to 24 hours or more, and storing refresh tokens in localStorage where they are accessible to JavaScript and therefore vulnerable to XSS. I covered this in full in our JWT vs Session Cookies guide . The audit version:

Java · Spring Security JWT configuration audit points

// CHECK 1 — Are you using RS256? (asymmetric) or HS256 with a shared secret?
// HS256 requires every service to share the signing key — one leak compromises all.
// RS256: sign with private key, verify with public key — services only need public key.
// SecurityConfig — enforce RS256 decoder, not symmetric key
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder
.withPublicKey(rsaPublicKey())       // RSA public key from config
.signatureAlgorithm(SignatureAlgorithm.RS256)  // hardcode the algorithm
.build();
}
// CHECK 2 — Token expiry in your JwtEncoder / token service
// Access token: max 15 minutes
// Refresh token: max 7-30 days, stored in HttpOnly cookie
// CHECK 3 — Are you accepting alg:none?
// Never use: Jwts.parserBuilder().setSigningKey(key).build()
// (trusts the token's own alg header)
// Always use: explicit decoder with hardcoded algorithm (as above)
application.yml · token expiry configuration

jwt:
access-token-expiry: 900        # 15 minutes in seconds — max acceptable
refresh-token-expiry: 604800    # 7 days in seconds
algorithm: RS256                # never HS256 for distributed services
# private-key-path and public-key-path from environment variables, not hardcoded

Check #8 — Restrict CORS and Cross-Origin Browser Access Explicitly

CORS misconfiguration is the security issue that looks harmless in development and is dangerous in production. allowedOrigins("*") means any website can make authenticated requests to your API from a user's browser using their existing session. Combined with CSRF, this enables cross-site request forgery attacks from any origin.

Java · Spring Boot CORS — explicit allowlist, never wildcard in production

// BAD — allows any origin, any method, any header
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");              // ← never in production
config.addAllowedMethod("*");
config.addAllowedHeader("*");
// ...
}
// GOOD — explicit allowlist per environment
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
// Read origins from environment variable — different per environment
config.setAllowedOrigins(List.of(
"https://app.yourdomain.com",
"https://admin.yourdomain.com"
));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH"));
config.setAllowedHeaders(List.of("Authorization", "Content-Type", "X-Request-ID"));
config.setAllowCredentials(true);          // required for cookies/auth headers
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return source;
}
🛠️

Test your CORS headers and preflight responses directly in the browser using a REST client (such as LearnHubly's REST API Tester ). Send an OPTIONS request to your endpoint and inspect the Access-Control-Allow-Origin response header — it should return your specific domain, not * .

Check #9 — Add Security Headers and Disable Verbose Error Leakage

Security headers are a 10-minute configuration change that protect against clickjacking, MIME-type sniffing, and XSS with almost zero application impact. Error leakage — stack traces, SQL error messages, and Spring exception details in API responses — gives attackers a map of your internals. Both are consistently missing in production systems I review.

Java · Spring Security headers + error leakage prevention

http.headers(headers -> headers
// X-Frame-Options: DENY — prevent clickjacking
.frameOptions(frame -> frame.deny())
// X-Content-Type-Options: nosniff — prevent MIME sniffing
.contentTypeOptions(Customizer.withDefaults())
// Strict-Transport-Security — force HTTPS for 2 years
.httpStrictTransportSecurity(hsts -> hsts
.maxAgeInSeconds(63072000)
.includeSubDomains(true)
.preload(true))
// Content-Security-Policy — restrict resource loading
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'; frame-ancestors 'none'"))
);
application.yml · suppress Spring Boot error detail in production

server:
error:
include-message: never          # never expose exception messages
include-binding-errors: never   # never expose field binding errors
include-stacktrace: never       # absolutely never expose stack traces
include-exception: false        # never expose exception class name
# Custom error controller overrides /error endpoint
# Return only: { "status": 400, "error": "Bad Request" } — nothing else

Check #10 — Run Dependency and Container Vulnerability Scans Before Deployment

Your own code is not the only attack surface. Every dependency in your pom.xml or build.gradle , every transitive dependency it pulls, and the base Docker image you deploy on — all of these have CVE databases maintained against them. A single unpatched dependency can expose your application to a critical vulnerability that has nothing to do with your code.

Maven · OWASP Dependency-Check in pom.xml
<plugin><groupId>org.owasp
</groupId><artifactId>dependency-check-maven
</artifactId><version>9.0.7
</version><configuration><failBuildOnCVSS>7
</failBuildOnCVSS><format>HTML
</format><suppressionFile>dependency-check-suppressions.xml
</suppressionFile></configuration><executions><execution><goals><goal>check
</goal></goals></execution></executions></plugin>
Shell · Trivy container scan before pushing to registry

# Scan your Docker image — fail if HIGH or CRITICAL CVEs found
trivy image --exit-code 1 --severity HIGH,CRITICAL
your-app:latest
# Scan filesystem for dependency CVEs (no image needed)
trivy fs --exit-code 1 --severity HIGH,CRITICAL .
# Snyk — alternative with fix suggestions
snyk test --severity-threshold=high
CI pipeline integration

These scans should run on every build — not quarterly. New CVEs are disclosed daily. A dependency clean on Monday may be critical by Friday. Integrate OWASP Dependency-Check or Snyk into your CI pipeline and fail the build on HIGH+ findings. The Spring Boot dependency version management documentation lists every managed dependency — use it to understand what version is active in your classpath.

Real Spring Boot Security Misconfigurations I Keep Seeing in Production Projects

The ten checks above are systematic. These are the patterns that appear so consistently they deserve their own section — because they slip through even when teams think they have covered the basics.

1. H2 console enabled in production

spring.h2.console.enabled=true in application.properties with no profile guard. The H2 web console is a full database interface — accessible in a browser, no authentication required by default. Teams enable it for local development and forget to disable it. Profile your configuration: development-only settings belong in application-dev.yml , never in the base file.

2. Spring Security @EnableWebSecurity without method security

Route-level security in SecurityFilterChain protects URL patterns. It does not protect service methods called from scheduled tasks, event listeners, or internal service-to-service calls that bypass HTTP entirely. Add @EnableMethodSecurity(prePostEnabled=true) and annotate critical service methods with @PreAuthorize .

3. session fixation and CSRF disabled without replacement

http.csrf(csrf -> csrf.disable()) is correct for stateless JWT APIs — but teams disable it for session-based applications where it is still necessary, or they forget to verify their API is truly stateless before disabling. If you are using server-side sessions, never disable CSRF protection without a replacement strategy.

4. Logging sensitive request data

logging.level.org.springframework.web=DEBUG in production logs full request and response bodies including Authorization headers, tokens, and request parameters containing user data. Debug logging should never be active in production. Use structured logging with explicit field selection.

5. Docker image running as root

The default Spring Boot Docker image runs as root inside the container. If the container is compromised, the attacker has root access to the container filesystem. Always add a non-root user in your Dockerfile and switch to it before the entrypoint.

Dockerfile · run Spring Boot as non-root user

FROM eclipse-temurin:21-jre-alpine
# Create non-root user
RUN addgroup -S spring && adduser -S spring -G spring
# Copy application as non-root
COPY --chown=spring:spring target/app.jar /app/app.jar
# Switch to non-root before entrypoint
USER spring:spring
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

Common False Security Assumptions That Keep Spring Boot Apps Vulnerable

Before the checklist — the six assumptions I hear most often in project reviews that turn out to be wrong:

  • "We use Spring Security, so our endpoints are protected." — Not if your SecurityFilterChain ends with anyRequest().permitAll() or a route was accidentally excluded from the matcher pattern.
  • "Actuator is only reachable internally." — Internal URLs leak through logs, screenshots, proxies, browser history, and shared documentation. "Only internal" is not access control.
  • "Hibernate prevents all SQL injection automatically." — Only when you use parameterised queries. Native queries with concatenation, dynamic JPQL, and sort parameters bypass Hibernate's protection entirely.
  • \napplication.yml inside a private repo is safe enough." — Repository access spreads: contractors, CI systems, forked branches, export archives, and leaked tokens. Committed secrets are eventually recoverable.
  • "Default CORS settings are fine for frontend integration." — During development, yes. In production, permissive CORS allows any website to make authenticated requests from a user's browser using their credentials.
  • "We will run dependency scans before release." — Quarterly scans or release-gate scans are not security coverage. A vulnerability disclosed on Tuesday does not wait for your release process.

Almost every preventable production issue starts as one of these six assumptions surviving long enough to become infrastructure truth.

The 10-Minute Pre-Production Spring Boot Security Checklist

Print this. Add it to your PR template. Run it before every production deployment. Every item maps to a check above.

  • Spring Boot parent version is current (3.3.x+) and explicitly pinned in pom.xml Check #1
  • Spring Security version verified — no known HIGH/CRITICAL CVEs via Dependency-Check Check #1
  • Actuator exposes only health + info — all other endpoints disabled or ADMIN-only Check #2
  • Actuator runs on port 8081 — not the public-facing 8080 Check #2
  • SecurityFilterChain ends with anyRequest().authenticated() — not permitAll() Check #3
  • Admin and internal routes explicitly protected with role-based rules Check #3
  • No secrets, passwords, or API keys in any committed configuration file Check #4
  • All secrets sourced from environment variables or a secrets manager Check #4
  • Grep audit finds zero + concatenation in query construction code Check #5
  • Semgrep java.sql.injection rules passing in CI pipeline Check #5
  • Every @RequestBody parameter has @Valid — confirmed with a test case Check #6
  • Global @RestControllerAdvice handles MethodArgumentNotValidException cleanly Check #6
  • JWT uses RS256 — not HS256 with a shared secret Check #7
  • Access token expiry is 15 minutes or less Check #7
  • Refresh tokens stored in HttpOnly + Secure cookies — not localStorage Check #7
  • CORS configured with explicit origin allowlist — no wildcard Check #8
  • Security headers present: HSTS, X-Frame-Options, X-Content-Type-Options, CSP Check #9
  • server.error.include-stacktrace=never in production config Check #9
  • OWASP Dependency-Check or Snyk passing with no HIGH/CRITICAL findings Check #10
  • Docker image scanned with Trivy — no HIGH/CRITICAL CVEs in base image Check #10
  • Dockerfile uses non-root USER before ENTRYPOINT Misconfig #5
  • H2 console disabled in all non-development profiles Misconfig #1
  • Debug logging disabled — no sensitive headers or bodies logged in production Misconfig #4

FAQ — Spring Boot Application Security

Is Spring Boot secure by default?

No. Spring Boot provides security building blocks — Spring Security, Actuator, Bean Validation — but none are configured for production security by default. Actuator endpoints may be exposed without authentication, the SecurityFilterChain can permit unauthenticated access if misconfigured, CORS defaults are permissive in development, and secrets default to plaintext in properties files. Secure configuration requires deliberate setup. Adding spring-boot-starter-security is the beginning of the work, not the end.

Are Spring Boot Actuator endpoints dangerous in production?

Yes, when left with default or broad exposure. Endpoints like /actuator/env , /actuator/heapdump , /actuator/beans , and /actuator/mappings expose environment variables including secrets, full JVM memory dumps, bean wiring details, and complete route maps. In production, expose only /actuator/health and /actuator/info . Run Actuator on a separate internal port (8081) and ensure that port is never reachable from outside your VPC or load balancer.

Is Spring Security enough to secure a Spring Boot API?

Spring Security is necessary but not sufficient. It handles authentication and route-level authorization — but SQL injection, hardcoded secrets, insecure dependencies, missing input validation, CORS misconfiguration, and container vulnerabilities are all outside Spring Security's scope. A secure Spring Boot application requires correct configuration across all layers: the Security filter chain, application code, infrastructure, and dependency management.

How often should I run Spring Boot dependency vulnerability scans?

On every build in your CI pipeline — not quarterly. OWASP Dependency-Check, Snyk, or Trivy should run as a pipeline step and fail the build if a HIGH or CRITICAL CVE is detected. New CVEs are disclosed daily. A dependency that was clean on Monday may have a critical disclosure by Friday. Quarterly scans create a false sense of coverage and miss the vulnerabilities disclosed between runs.

Can AI-generated Spring Boot code introduce security bugs?

Yes, consistently. AI coding assistants generate Spring Boot code from patterns in public training data that includes older insecure configurations. Common outputs include anyRequest().permitAll() as the SecurityFilterChain default, allowedOrigins("*") for CORS, string-concatenated SQL queries, and hardcoded JWT secrets in properties examples. Treat AI-generated security-related code as untrusted input: review it, lint it with Semgrep, and test it with real attack payloads before merging.

Security Is Not a Spring Dependency — It Is a Deployment Discipline

Most Spring Boot compromises do not begin with advanced attackers discovering obscure framework flaws. They begin with teams shipping ordinary defaults, postponing small reviews, and trusting that "someone else already checked security."

An exposed Actuator endpoint survives because it was convenient during testing.
A hardcoded credential survives because moving it to Vault was scheduled for "later."
A vulnerable dependency survives because no scanner was part of CI.
A raw query survives because it worked and nobody fuzzed it before merge.

None of these decisions feel catastrophic in the sprint where they are made. Combined, they become the exact attack surface production systems carry for months.

That is why this checklist matters. Not because Spring Boot is insecure — but because secure frameworks still ship insecure applications when review discipline is optional.

The ten checks above take under ten minutes. Make them mandatory. — Priya

Run These 3 Browser Checks Before Every Production Deployment

Three minutes of browser testing catches more than most teams expect.

  • 🗄️ Review SQL and native queries with the SQL Formatter — concatenation risks become visible when code is structured
  • 🛠️ Fire real attack payloads with the REST API Tester — test CORS headers, rate limiting, and injection responses
  • 🔍 Decode suspicious encoded requests with the URL Decoder — reveal what attackers are actually sending in your logs
Third-Party Links Disclaimer

This article may contain links to third-party websites, documentation, tools, or services for reference and additional information. These external resources are maintained by their respective owners, and LearnHubly does not control or guarantee their availability, accuracy, security, or content. Please review the terms and privacy policies of third-party websites before using their services.

Priya Singh

Java
Spring Boot
React
APIs

Principal Software Engineer • 15+ Years Experience

Priya Singh is a Principal Software Engineer with 15+ years of experience building scalable applications and developer tools. She specializes in backend architecture, APIs, and performance optimization.