Introduction
Spring Security is essential for securing modern Java applications, but its exceptions can feel cryptic if you’re not prepared. Whether it’s failed logins or unauthorized access, each exception comes with a specific HTTP status and error code that can guide both debugging and user feedback.
In this guide, we’ll demystify common Spring Security exceptions, explain why they happen, and show how to handle them effectively—with real-world code examples.
⚙️ Spring Security: Default Exception Mappings
Spring Security intercepts failed authentication and authorization attempts, mapping exceptions to standardized HTTP responses.
Exception Class | HTTP Status | Error Code |
---|---|---|
AccessDeniedException | 403 | security.access_denied |
AccountExpiredException | 400 | security.account_expired |
AuthenticationCredentialsNotFoundException | 401 | security.auth_required |
AuthenticationServiceException | 500 | security.internal_error |
BadCredentialsException | 400 | security.bad_credentials |
UsernameNotFoundException | 400 | security.bad_credentials |
InsufficientAuthenticationException | 401 | security.auth_required |
LockedException | 400 | security.user_locked |
DisabledException | 400 | security.user_disabled |
Others | 500 | unknown_error |
🔐 1. AccessDeniedException (403)
Scenario: A user is authenticated but lacks the required authority.
Example:
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) {
// Only accessible by ADMIN users
}
Custom Handler:
@ControllerAdvice
public class SecurityExceptionHandler {
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<String> handleAccessDenied() {
return ResponseEntity.status(403).body("Access Denied: You lack the required roles!");
}
}
⌛ 2. AccountExpiredException (400)
Scenario: The user account has expired (e.g., due to trial period expiry).
Example:
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByUsername(username);
if (user.isExpired()) {
throw new AccountExpiredException("Account expired");
}
return user;
}
}
Response:
{
"status": 400,
"error_code": "security.account_expired"
}
🔑 3. AuthenticationCredentialsNotFoundException (401)
Scenario: A protected endpoint is accessed without authentication.
Example:
@GetMapping("/admin")
public String adminPanel() {
return "admin"; // Requires authentication
}
Fix:
- Secure the endpoint properly (
@Secured
,@PreAuthorize
). - Ensure your security config permits anonymous access only where intended.
❌ 4. BadCredentialsException (400)
Scenario: Wrong username or password during login.
Example:
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.username(), request.password())
);
} catch (BadCredentialsException e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid credentials", e);
}
return ResponseEntity.ok("Login successful");
}
Response:
{
"status": 400,
"error_code": "security.bad_credentials"
}
🔒 5. LockedException & DisabledException (400)
Scenario:
LockedException
: Account locked due to suspicious activity or failed login attempts.DisabledException
: Account has been manually deactivated.
Example:
@Override
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByUsername(username);
if (!user.isEnabled()) {
throw new DisabledException("Account disabled");
}
if (user.isLocked()) {
throw new LockedException("Account locked");
}
return user;
}
✅ Best Practices for Handling Security Exceptions
- Use
@ControllerAdvice
: Centralize error handling across your app. - Return JSON Responses: Use
@RestControllerAdvice
for REST APIs. - Avoid Leaking Sensitive Info: Don’t expose whether the username or password was wrong.
- Log Security Events: Track failed logins or suspicious access for audits.
- Use Standard Error Codes: Helps frontend apps handle errors predictably.
❓ FAQ Section
Q1: Why does UsernameNotFoundException
map to security.bad_credentials
?
👉 To prevent username enumeration attacks by hiding whether a username exists.
Q2: How do I return JSON instead of an HTML error page?
👉 Use @RestControllerAdvice
and return ResponseEntity<Map<String, Object>>
.
Q3: Can I override Spring Security’s default error codes?
👉 Yes! You can extend DefaultAuthenticationEventPublisher
or create a custom AuthenticationFailureHandler
.
🏁 Conclusion
Mastering Spring Security exceptions allows you to secure your app and deliver clear, user-friendly responses. By understanding default behaviors and applying customization techniques, you can improve both developer experience and user trust.
🔑 Keywords: Spring Security exceptions, BadCredentialsException, AccessDeniedException handler, Spring Security error response, @ControllerAdvice for security, Spring Boot secure login errors.
🚀 Why Trust This Guide?
- Based on Spring Security 6.x documentation
- Includes real-world use cases and patterns
- Balances security with developer usability
Build secure, resilient, and user-aware applications—one exception at a time. 🔒✨