eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – Spring Sale 2026 – NPI EA (cat= Baeldung)
announcement - icon

Yes, we're now running our Spring Sale. All Courses are 30% off until 31st March, 2026

>> EXPLORE ACCESS NOW

Partner – Diagrid – NPI EA (cat= Testing)
announcement - icon

In distributed systems, managing multi-step processes (e.g., validating a driver, calculating fares, notifying users) can be difficult. We need to manage state, scattered retry logic, and maintain context when services fail.

Dapr Workflows solves this via Durable Execution which includes automatic state persistence, replaying workflows after failures and built-in resilience through retries, timeouts and error handling.

In this tutorial, we'll see how to orchestrate a multi-step flow for a ride-hailing application by integrating Dapr Workflows and Spring Boot:

>> Dapr Workflows With PubSub

Course – Spring Sale 2026 – NPI (cat=Baeldung)
announcement - icon

Yes, we're now running our Spring Sale. All Courses are 30% off until 31st March, 2026

>> EXPLORE ACCESS NOW

1. Overview

When building web applications that deal with sensitive data, it’s important to ensure the security of user passwords. One important aspect of password security is checking whether a password is compromised, often due to its presence in a data breach.

Spring Security 6.3 introduces a new feature that allows us to easily check if a password has been compromised.

In this tutorial, we’ll explore the new CompromisedPasswordChecker API in Spring Security and how it can be integrated in our Spring Boot application.

2. Understanding Compromised Passwords

A compromised password is a password that’s exposed in a data breach, making it vulnerable to unauthorized access. Attackers often use these compromised passwords in credential stuffing and password stuffing attacks, using the leaked username-password pairs across multiple sites or common passwords against multiple accounts.

To mitigate this risk, it’s crucial to check if a user’s password is compromised before creating an account against it.

It’s also important to note that a previously valid password can become compromised over time, so it’s always recommended to check for compromised passwords not only during account creation but also during the login process or any process that allows the user to change their passwords. We can prompt a user to reset their password, if a login attempt fails due to detection of a compromised password.

3. The CompromisedPasswordChecker API

Spring Security provides a simple CompromisedPasswordChecker interface for checking if a password has been compromised:

public interface CompromisedPasswordChecker {
    CompromisedPasswordDecision check(String password);
}

This interface exposes a single check() method that takes a password as an input and returns an instance of CompromisedPasswordDecision, indicating whether the password is compromised.

The check() method expects a plaintext password, so we’ll have invoke it before we encrypt our password using a PasswordEncoder.

3.1. Configuring the CompromisedPasswordChecker Bean

To enable compromised password checking in our application, we need to declare of bean of type CompromisedPasswordChecker:

@Bean
public CompromisedPasswordChecker compromisedPasswordChecker() {
    return new HaveIBeenPwnedRestApiPasswordChecker();
}

The HaveIBeenPwnedRestApiPasswordChecker is the default implementation of CompromisedPasswordChecker provided by Spring Security.

This default implementation, integrates with the popular Have I Been Pwned API, which maintains an extensive database of compromised passwords from data breaches.

When the check() method of this default implementation is invoked, it securely hashes the provided password and sends the first 5 characters of the hash to the Have I Been Pwned API. The API responds with a list of hash suffixes that match this prefix. The method then compares the full hash of the password against this list and determines if it’s compromised. The entire check is performed without ever sending the plaintext password over the network.

3.2. Customizing the CompromisedPasswordChecker Bean

If our application uses a proxy server for egress HTTP requests, we can configure the HaveIBeenPwnedRestApiPasswordChecker with a custom RestClient:
@Bean
public CompromisedPasswordChecker customCompromisedPasswordChecker() {
    RestClient customRestClient = RestClient.builder()
      .baseUrl("https://api.proxy.com/password-check")
      .defaultHeader("X-API-KEY", "api-key")
      .build();

    HaveIBeenPwnedRestApiPasswordChecker compromisedPasswordChecker = new HaveIBeenPwnedRestApiPasswordChecker();
    compromisedPasswordChecker.setRestClient(customRestClient);
    return compromisedPasswordChecker;
}

Now, when we call the check() method of our CompromisedPasswordChecker bean in our application, It’ll send the API request to the base URL we’ve defined along with the custom HTTP header.

4. Handling Compromised Passwords

Now that we’ve configured our CompromisedPasswordChecker bean, let’s look at how we can use it in our service layer to validate passwords. Let’s take a common use case of a new user registration:

@Autowired
private CompromisedPasswordChecker compromisedPasswordChecker;

String password = userCreationRequest.getPassword();
CompromisedPasswordDecision decision = compromisedPasswordChecker.check(password);
if (decision.isCompromised()) {
    throw new CompromisedPasswordException("The provided password is compromised and cannot be used.");
}

Here, we simply invoke the check() method with the plaintext password provided by the client and examine the returned CompromisedPasswordDecision. If the isCompromised() method returns true, we throw a CompromisedPasswordException to abort the registration process.

5. Handling the CompromisedPasswordException

When our service layer throws a CompromisedPasswordException, we’d want to handle it gracefully and provide feedback to the client.

One way to do this is to define a global exception handler in a @RestControllerAdvice class:

@ExceptionHandler(CompromisedPasswordException.class)
public ProblemDetail handle(CompromisedPasswordException exception) {
    return ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, exception.getMessage());
}

When this handler method catches a CompromisedPasswordException, it returns an instance of ProblemDetail class, which constructs an error response that’s compliant with the RFC 9457 specification:

{
    "type": "about:blank",
    "title": "Bad Request",
    "status": 400,
    "detail": "The provided password is compromised and cannot be used.",
    "instance": "/api/v1/users"
}

6. Custom CompromisedPasswordChecker Implementation

While the HaveIBeenPwnedRestApiPasswordChecker implementation is a great solution, there might be scenarios where we want to integrate with a different provider, or even implement our own compromised password checking logic.

We can do so by implementing the CompromisedPasswordChecker interface:

public class PasswordCheckerSimulator implements CompromisedPasswordChecker {
    public static final String FAILURE_KEYWORD = "compromised";

    @Override
    public CompromisedPasswordDecision check(String password) {
        boolean isPasswordCompromised = false;
        if (password.contains(FAILURE_KEYWORD)) {
            isPasswordCompromised = true;
        }
        return new CompromisedPasswordDecision(isPasswordCompromised);
    }
}

Our sample implementation considers a password compromised if it contains the word “compromised”. While not very useful in a real-world scenario, it demonstrates how straightforward it’s to plug in our own custom logic.

In our test cases, it’s generally a good practice to use such simulated implementation instead of making an HTTP call to an external API. To use our custom implementation in our tests, we can define it as a bean in a @TestConfiguration class:

@TestConfiguration
public class TestSecurityConfiguration {
    @Bean
    public CompromisedPasswordChecker compromisedPasswordChecker() {
        return new PasswordCheckerSimulator();
    }
}

In our test classes, where we want to use this custom implementation, we’ll annotate it with @Import(TestSecurityConfiguration.class).

Also, to avoid BeanDefinitionOverrideException when running our tests, we’ll annotate our main CompromisedPasswordChecker bean with @ConditionalOnMissingBean annotation.

Finally, to verify the behaviour of our custom implementation, we’ll write a test case:

@Test
void whenPasswordCompromised_thenExceptionThrown() {
    String emailId = RandomString.make() + "@baeldung.it";
    String password = PasswordCheckerSimulator.FAILURE_KEYWORD + RandomString.make();
    String requestBody = String.format("""
            {
                "emailId"  : "%s",
                "password" : "%s"
            }
            """, emailId, password);

    String apiPath = "/users";
    mockMvc.perform(post(apiPath).contentType(MediaType.APPLICATION_JSON).content(requestBody))
      .andExpect(status().isBadRequest())
      .andExpect(jsonPath("$.status").value(HttpStatus.BAD_REQUEST.value()))
      .andExpect(jsonPath("$.detail").value("The provided password is compromised and cannot be used."));
}

7. Creating a Custom @NotCompromised Annotation

As discussed earlier, we should check for compromised passwords not only during user registration, but in all APIs that allow a user to change their passwords or authenticate using their password, such as the login API.

While we can perform this check in the service layer for each of these processes, using a custom validation annotation provides a more declarative and reusable approach.

First, let’s define a custom @NotCompromised annotation:

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CompromisedPasswordValidator.class)
public @interface NotCompromised {
    String message() default "The provided password is compromised and cannot be used.";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Next, let’s implement the ConstraintValidator interface:

public class CompromisedPasswordValidator implements ConstraintValidator<NotCompromised, String> {
    @Autowired
    private CompromisedPasswordChecker compromisedPasswordChecker;

    @Override
    public boolean isValid(String password, ConstraintValidatorContext context) {
        CompromisedPasswordDecision decision = compromisedPasswordChecker.check(password);
        return !decision.isCompromised();
    }
}

We autowire an instance of the CompromisedPasswordChecker class and use it to check if the client’s password is compromised.

We can now use our custom @NotCompromised annotation on the password fields of our request bodies and validate their values:

@NotCompromised
private String password;
@Autowired
private Validator validator;

UserCreationRequestDto request = new UserCreationRequestDto();
request.setEmailId(RandomString.make() + "@baeldung.it");
request.setPassword(PasswordCheckerSimulator.FAILURE_KEYWORD + RandomString.make());

Set<ConstraintViolation<UserCreationRequestDto>> violations = validator.validate(request);

assertThat(violations).isNotEmpty();
assertThat(violations)
  .extracting(ConstraintViolation::getMessage)
  .contains("The provided password is compromised and cannot be used.");

8. Conclusion

In this article, we explored how we can use Spring Security’s CompromisedPasswordChecker API to enhance our application’s security by detecting and preventing the use of compromised passwords.

We discussed how to configure the default HaveIBeenPwnedRestApiPasswordChecker implementation. We also talked about customizing it for our specific environment, and even implement our own custom compromised password checking logic.

In conclusion, checking for compromised passwords adds an extra layer of protection for our users’ accounts against potential security attacks.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

Course – Spring Sale 2026 – NPI EA (cat= Baeldung)
announcement - icon

Yes, we're now running our Spring Sale. All Courses are 30% off until 31st March, 2026

>> EXPLORE ACCESS NOW

Course – Spring Sale 2026 – NPI (All)
announcement - icon

Yes, we're now running our Spring Sale. All Courses are 30% off until 31st March, 2026

>> EXPLORE ACCESS NOW

eBook Jackson – NPI EA – 3 (cat = Jackson)