RadhaPublication
HomeInterviewLearning PathsRole TracksMockBooksPricing
Login / Sign UpLog in

© 2026 Radha Publication. All rights reserved.

Shop on AmazonDonateContactPrivacyTerms
Spring Boot questions
MediumSpring Boot#547 min readJul 11, 2026

Password Encoding.

practice
learning
Practice modeTest yourself instead of reading straight through

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.

🧠 Memory Map
Memory map — visual summary of this topic

What it is

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.

How it works under the hood

  1. The user types a raw password, such as p@ssw0rd.
  2. Your application receives it during sign-up or password change.
  3. You call encode(rawPassword) on a PasswordEncoder bean.
  4. The encoder adds a salt (a random value mixed into the hash so equal passwords do not produce equal stored values) and applies a slow algorithm such as bcrypt.
  5. The encoded string is stored in the database, not the raw password.
  6. During login, you load the stored value and call matches(rawPassword, storedHash).
  7. Spring Security recomputes the hash using the stored parameters and compares safely, returning true or false.
  8. If you use DelegatingPasswordEncoder, the stored value starts with an id like {bcrypt}, so Spring knows which encoder to use.

Why bcrypt is the default choice

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.

Comparison of common options

OptionReversible?Good for passwords?Typical use
Plain textNoNoNever
EncryptionYesNoSecrets that must be recovered
HashingNoYesPassword storage

Choosing an encoder in Spring Boot

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.

When and why to use it

  • Use password encoding whenever you store a login password.
  • Use it at write time, not just at login time.
  • Use it even in internal apps; insider leaks are real.
  • Use matches() for verification instead of comparing strings yourself.

Important edge cases

  • Missing prefix: if you use DelegatingPasswordEncoder and store a bcrypt hash without {bcrypt}, login can fail with a mapping error.
  • Same password, different hash: because of salt, two users with the same password should still get different stored values.
  • Algorithm upgrade: Spring Security has upgradeEncoding() so you can re-hash old passwords after a successful login if your policy changes.
  • Thread safety: the encoder is typically a singleton bean; you do not need a new encoder per request.

Performance and complexity

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.

Real-world story

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.

Spring Boot
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:

  • How is password encoding different from encryption? Encryption is reversible with a key, but password hashing is one-way. For passwords, you want verification, not recovery.
  • Why does Spring Security use 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.
  • What does the salt do? It makes the same password produce a different stored value each time, which defeats rainbow tables and stops attackers from spotting identical passwords across users.
  • How do you migrate old passwords? Keep the old encoded value, verify it with the right encoder, and after a successful login re-encode it with your new policy. Spring Security’s delegating approach is built for this.
  • When should I choose bcrypt, PBKDF2, or Argon2? Bcrypt is the common default and a strong general choice; PBKDF2 is often used where compliance or existing platform support matters; Argon2 is attractive when you want a memory-hard algorithm that is harder to brute-force on specialized hardware.
  • Can I store the output of 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:

  • If two users choose the same password, will their hashes be identical? No, not with a proper salted encoder like bcrypt. That is exactly why salts exist.
  • Can I call 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().
  • Is a stronger cost factor always better? Not always. Higher cost slows attackers, but it also slows every login and can hurt your auth service if you choose a value that is too expensive for your traffic.

Common Mistakes:

  • Storing raw passwords: the correction is to store only the encoded hash, never the plain password.
  • Using encryption for passwords: the correction is to use hashing, because you do not need to decrypt a password later.
  • Comparing strings directly: the correction is to call PasswordEncoder.matches(), which is aware of salts and algorithm details.
  • Forgetting the prefix with delegating encoders: the correction is to store the full value, such as {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:

  • Use PasswordEncoder, not custom hashing code.
  • Encode on sign-up or password change.
  • Verify with matches() at login.
  • Prefer a strong, salted, slow algorithm such as bcrypt.
  • Store the full encoded string, including any algorithm prefix.
  • Plan for migration and re-hashing when policies change.

Practice Tasks:

  • Build a tiny Spring Boot app that registers a user and stores only an encoded password in an in-memory map.
  • Add a login endpoint that uses matches() and returns 200 for the right password and 401 for the wrong one.
  • Simulate a legacy database row without {bcrypt}, then fix it by adding the prefix and observing the login succeed.
Previous
Back to Questions←→to navigate
Next

Recommended Resources

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

Recommended Book

Cracking the Coding Interview

189 Programming Questions and Solutions

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)); }; } }