Spring Security Exceptions Explained: Error Codes, Scenarios & Fixes

Error Codes, Scenarios & Fixes

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 ClassHTTP StatusError Code
AccessDeniedException403security.access_denied
AccountExpiredException400security.account_expired
AuthenticationCredentialsNotFoundException401security.auth_required
AuthenticationServiceException500security.internal_error
BadCredentialsException400security.bad_credentials
UsernameNotFoundException400security.bad_credentials
InsufficientAuthenticationException401security.auth_required
LockedException400security.user_locked
DisabledException400security.user_disabled
Others500unknown_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

  1. Use @ControllerAdvice: Centralize error handling across your app.
  2. Return JSON Responses: Use @RestControllerAdvice for REST APIs.
  3. Avoid Leaking Sensitive Info: Don’t expose whether the username or password was wrong.
  4. Log Security Events: Track failed logins or suspicious access for audits.
  5. 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. 🔒✨

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *