Hook: Interviewers love this question because a tiny mistake here can expose every user account in your system.
Question: What is password encoding in Spring Boot, and how should you use it correctly with Spring Security?
Answer: Password encoding means turning a plain password into a one-way hash before storing it. In Spring Boot, you inject a PasswordEncoder, call encode() when a password is created or changed, and call matches() when a user logs in. You never try to decrypt it; you only verify whether the raw password matches the stored encoded value.
Interview-Ready Answer: In Spring Boot, I store passwords as one-way hashes, not plain text. I usually expose a PasswordEncoder bean, often via DelegatingPasswordEncoder, then call encode() at registration and matches() at login. The important detail is that the encoded value includes algorithm information, like {bcrypt}, which helps Spring Security verify old and new hashes safely during migration.
Passwords should be stored as hashes, meaning one-way fingerprints. A hash is not encryption: encryption is reversible with a key, but a password hash is designed so you cannot get the original password back. Spring Security gives you the PasswordEncoder interface for this exact job.
p@ssw0rd.encode(rawPassword) on a PasswordEncoder bean.matches(rawPassword, storedHash).DelegatingPasswordEncoder, the stored value starts with an id like {bcrypt}, so Spring knows which encoder to use.Bcrypt is intentionally slow. That is a feature, not a bug: it makes brute-force attacks much more expensive. A typical strength of 10 often lands in the tens to low hundreds of milliseconds per hash on a normal server, and each +1 cost roughly doubles the work. That means login is still fast for humans, but too slow for attackers to test billions of guesses cheaply.
| Option | Reversible? | Good for passwords? | Typical use |
|---|---|---|---|
| Plain text | No | No | Never |
| Encryption | Yes | No | Secrets that must be recovered |
| Hashing | No | Yes | Password storage |
For most Spring Boot apps, the simplest safe choice is PasswordEncoderFactories.createDelegatingPasswordEncoder(). It uses a modern default and stores a prefix like {bcrypt}, which makes migrations easier. If you need a stricter policy, you can wire a specific encoder such as BCryptPasswordEncoder or a different algorithm supported by Spring Security.
matches() for verification instead of comparing strings yourself.DelegatingPasswordEncoder and store a bcrypt hash without {bcrypt}, login can fail with a mapping error.upgradeEncoding() so you can re-hash old passwords after a successful login if your policy changes.Hashing work is intentionally expensive. For bcrypt, the runtime cost is roughly exponential in the cost factor, while memory usage stays small and predictable. In practical terms, the system pays a small CPU tax on each password check, which is exactly what you want for security. For a busy auth service, this means you must size the login path carefully; if hashing takes 100 ms and you need 50 logins per second, you may need multiple CPU cores or more instances.
Memory hook: think of password encoding as putting a passport in a locked shredder with a secret machine setting; the shredder can check whether a new passport matches the old one, but it cannot rebuild the original paper.
Imagine a checkout service for an e-commerce app. The team migrates from an old custom auth system to Spring Security and switches to DelegatingPasswordEncoder to modernize password handling. Everything works in staging, but in production thousands of users suddenly get 401 responses on login.
What went wrong? The old database already contained bcrypt hashes, but they were stored without the required {bcrypt} prefix. Spring Security could not tell which encoder to use, so login started throwing IllegalArgumentException with a message like There is no PasswordEncoder mapped for the id 'null'. Support tickets spike, password reset emails flood the system, and the checkout funnel drops because returning users cannot sign in.
How it was fixed: the team added a migration step to prefix legacy bcrypt values correctly, then re-hashed passwords on next successful login. After that, authentication returned to normal and the logs stopped filling with encoder mapping errors.
package com.example.passwordencoding;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
@SpringBootApplication
public class PasswordEncodingApplication {
public static void main(String[] args) {
SpringApplication.run(PasswordEncodingApplication.class, args);
}
@Bean
PasswordEncoder passwordEncoder() {
// DelegatingPasswordEncoder is a safe default because it stores an id like {bcrypt}.
// That makes it clear which algorithm should verify the hash and helps with migrations.
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean
CommandLineRunner demo(PasswordEncoder passwordEncoder) {
return args -> {
String rawPassword = "s3cret!";
// Registration flow: encode once, store only the encoded value.
String storedHash = passwordEncoder.encode(rawPassword);
System.out.println("Stored hash: " + storedHash);
// Login flow: matches(raw, encoded) should return true for the right password.
System.out.println("Correct password matches? " + passwordEncoder.matches(rawPassword, storedHash));
System.out.println("Wrong password matches? " + passwordEncoder.matches("wrong-password", storedHash));
// Edge case: a legacy bcrypt hash without the {bcrypt} prefix.
// This is a common migration bug when switching to DelegatingPasswordEncoder.
String legacyBcryptWithoutPrefix = new BCryptPasswordEncoder().encode(rawPassword);
try {
boolean result = passwordEncoder.matches(rawPassword, legacyBcryptWithoutPrefix);
System.out.println("Legacy hash without prefix matches? " + result);
} catch (IllegalArgumentException ex) {
System.out.println("Legacy hash failed as expected: " + ex.getMessage());
}
// Fix: add the prefix so the delegating encoder knows how to verify it.
String legacyBcryptWithPrefix = "{bcrypt}" + legacyBcryptWithoutPrefix;
System.out.println("Legacy hash with prefix matches? " + passwordEncoder.matches(rawPassword, legacyBcryptWithPrefix));
};
}
}
Follow-up & Tricky Questions:
matches() instead of equals()? Because the stored hash is not the same text as the password, and a proper matcher must recompute and compare safely. Direct string comparison would never work for real hashes.encode() in plain text columns? Yes, because the encoded string is what you store; just make sure the column is large enough for the full hash plus prefix, and never truncate it.Tricky / gotcha questions:
encode(raw).equals(stored) to verify a password? No, because the encoder usually creates a new salt each time, so you would generate a different hash on every attempt. Use matches().Common Mistakes:
PasswordEncoder.matches(), which is aware of salts and algorithm details.{bcrypt}..., or migrate old records carefully.Memory Hook: “Passwords are for checking, not reading.” Or picture a locked shredder: you feed in the password once, keep the shredded result, and later use the shredder to test new guesses.
Cheat Sheet:
PasswordEncoder, not custom hashing code.matches() at login.Practice Tasks:
matches() and returns 200 for the right password and 401 for the wrong one.{bcrypt}, then fix it by adding the prefix and observing the login succeed.