Backend
7/1/2026
3 min read

Mastering Spring Security 7 with Spring Boot 4 and Java 21 – Part 3: Password Encoding and BCryptPasswordEncoder Explained

Mastering Spring Security 7 with Spring Boot 4 and Java 21 – Part 3: Password Encoding and BCryptPasswordEncoder Explained

In the previous article, we learned about authentication and authorization and implemented role-based access control using Spring Security 7.

However, storing passwords securely is equally important. Saving plain-text passwords is one of the biggest security risks in any application. If an attacker gains access to the database, all user credentials become exposed.

Spring Security provides powerful password encoding mechanisms that help us securely store passwords and protect user accounts.

In this article, we will understand password encoding, BCryptPasswordEncoder, DelegatingPasswordEncoder, and production-level password security practices.

Why Password Encoding is Important

Consider a User table:

Username

Password

john

password123

admin

admin123

If the database gets compromised, attackers can immediately see every user's password.

Instead, passwords should be stored as hashes:

Username

Password

john

$2a$10$U7df...

admin

$2a$10$P89a...

Even if the database is leaked, original passwords cannot easily be recovered.

What is Password Encoding?

Password encoding converts a plain password into a hashed value.

Plain password:

password123

Encoded password:

$2a$10$zAxK4R6P9gVh6R...

During login:

  1. User enters password.

  2. Spring Security encodes it internally.

  3. The encoded value is compared with the stored hash.

  4. Authentication succeeds if both match.

Understanding PasswordEncoder

Spring Security provides the PasswordEncoder interface.

publicinterfacePasswordEncoder {

Stringencode(CharSequencerawPassword);

booleanmatches(CharSequencerawPassword,
StringencodedPassword);

}

encode()

Converts plain password into a secure hash.

matches()

Compares the raw password with the stored hash.

BCryptPasswordEncoder

The most commonly used implementation is:

@Bean
publicPasswordEncoderpasswordEncoder() {
returnnewBCryptPasswordEncoder();
}

BCrypt provides:

  • Salting

  • Hashing

  • Adaptive strength

  • Protection against rainbow table attacks

It is recommended for most production applications.

Encoding Passwords

PasswordEncoderencoder=newBCryptPasswordEncoder();

StringencodedPassword=
encoder.encode("password123");

System.out.println(encodedPassword);

Output:

$2a$10$SDF78asd89Jks...

Notice that every execution generates a different value.

Why Does BCrypt Generate Different Hashes?

Example:

encoder.encode("password123");
encoder.encode("password123");

Output:

$2a$10$A4gf...
$2a$10$L9df...

Both hashes are different.

This happens because BCrypt uses a random salt internally.

This protects against precomputed dictionary attacks.

Verifying Passwords

PasswordEncoderencoder=
newBCryptPasswordEncoder();

Stringhash=
encoder.encode("password123");

booleanmatched=
encoder.matches(
"password123",
hash);

System.out.println(matched);

Output:

true

Spring Security compares the original password with the hash.

Using PasswordEncoder in UserDetailsService

@Bean
publicUserDetailsServiceuserDetailsService() {

UserDetailsuser=User.builder()
.username("john")
.password(passwordEncoder()
.encode("password123"))
.roles("USER")
.build();

returnnewInMemoryUserDetailsManager(user);
}

Without encoding:

.password("password123")

Spring Security throws:

There is no PasswordEncoder mapped for id "null"

Therefore, passwords should always be encoded.

Understanding DelegatingPasswordEncoder

Spring Security internally uses DelegatingPasswordEncoder.

Example:

{bcrypt}$2a$10$8fdK...

The prefix indicates which encoder was used.

Supported algorithms include:

  • bcrypt

  • pbkdf2

  • scrypt

  • noop

This allows migration between algorithms without breaking existing users.

Creating PasswordEncoder Bean

@Configuration
publicclassSecurityConfig {

    @Bean
publicPasswordEncoderpasswordEncoder() {
returnnewBCryptPasswordEncoder();
    }

}

Spring automatically injects this bean wherever required.

Real-World Example

Suppose users register through an API.

Request:

{
  "username":"john",
  "password":"password123"
}

Before saving:

user.setPassword(
passwordEncoder.encode(
request.getPassword()));

Stored in database:

$2a$10$Y67fsK...

At login:

passwordEncoder.matches(
requestPassword,
storedPassword);

This ensures passwords remain protected.

Common Mistakes

Storing Plain Passwords

Wrong:

user.setPassword("password123");

Correct:

user.setPassword(
passwordEncoder.encode(
"password123"));

Using NoOpPasswordEncoder

Avoid:

NoOpPasswordEncoder.getInstance();

It stores passwords in plain text and should only be used for testing.

Encrypting Passwords Instead of Hashing

Passwords should be hashed, not encrypted.

Hashing is one-way and much safer.

Production Best Practices

Use BCryptPasswordEncoder

newBCryptPasswordEncoder();

It provides strong protection and is widely accepted.

Never Log Passwords

Avoid:

System.out.println(password);

Passwords should never appear in logs.

Use Strong Password Policies

Require:

  • Minimum length

  • Uppercase letters

  • Lowercase letters

  • Numbers

  • Special characters

Enable Password Rotation

Allow users to reset passwords periodically.

Store Passwords Only in Encoded Form

Never save plain-text passwords in databases.

Summary

In this article, we learned:

  • Why password encoding is important.

  • PasswordEncoder interface.

  • BCryptPasswordEncoder.

  • Password matching.

  • Salting and hashing.

  • DelegatingPasswordEncoder.

  • Common mistakes.

  • Production-level best practices.

In Part 4, we will build a Custom UserDetailsService with Database Authentication using Spring Data JPA and MySQL, which is how most real-world applications manage users and credentials.

Tags

Enjoyed this article?

Subscribe to our newsletter for more backend engineering insights and tutorials.