skip to content

What is UserDetailsService in Spring Security, and what does loadUserByUsername return?

level: juniorimportance: must knowfreq 80%

answer

  1. one method: loadUserByUsername
  2. returns UserDetails, not a boolean
  3. throw UsernameNotFoundException, never return null
  4. carries encoded password + authorities + 4 flags
  5. retrieval only, no password check

basics

~20 s

UserDetailsService is an interface with one method, loadUserByUsername(String), that looks up a user by name and returns a UserDetails object holding the username, encoded password, and authorities (roles). It throws UsernameNotFoundException if no user exists.

solid answer

~30 s

UserDetailsService is Spring Security's single-method interface for loading user data during authentication: UserDetails loadUserByUsername(String username) throws UsernameNotFoundException. Spring calls it with the login name; you fetch the user from your store (memory, DB, LDAP) and return a UserDetails. UserDetails exposes getUsername(), getPassword() (the encoded hash), getAuthorities() (granted roles/permissions), plus four account-status flags: isEnabled, isAccountNonExpired, isAccountNonLocked, isCredentialsNonExpired. It carries no plaintext password and does no password comparison itself — that is the AuthenticationProvider's job. If the user is absent you must throw UsernameNotFoundException; you must never return null. Spring ships User.builder() (User.withUsername(...)) as a ready UserDetails implementation.

code

java · 19 lines
java
import org.springframework.security.core.userdetails.*;

public class MyUserDetailsService implements UserDetailsService {

    private final AccountRepository repo;

    public MyUserDetailsService(AccountRepository repo) { this.repo = repo; }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Account a = repo.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
        return User.withUsername(a.getUsername())
                   .password(a.getPasswordHash())   // already encoded (e.g. BCrypt)
                   .authorities(a.getAuthorities())  // e.g. "ROLE_USER"
                   .disabled(!a.isActive())
                   .build();
    }
}

go deeper

for a junior

Know the single method, that it returns UserDetails, and that it throws UsernameNotFoundException.

for a middle

Explain the four status flags and the never-return-null rule, and that password checks happen elsewhere.

for a senior

Discuss adapting a domain entity to UserDetails, authority/role prefixing, and case normalization.

for a principal

Frame it as the pluggable retrieval seam and reason about enumeration/timing implications handled downstream.

**UserDetailsService** is a core Spring Security interface (package `org.springframework.security.core.userdetails`) whose entire contract is a single method: ```java UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; ``` Its job is *retrieval only*: given a username (the login identifier the user typed), find the corresponding account and return it as a **UserDetails**. It does **not** verify the password — comparing the submitted password against the stored hash is the responsibility of the `AuthenticationProvider` (typically `DaoAuthenticationProvider`, see the related question). **What UserDetails carries** (interface `org.springframework.security.core.userdetails.UserDetails`): - `String getUsername()` — the account identifier. - `String getPassword()` — the **encoded** (hashed) password, e.g. a BCrypt hash. Never plaintext. - `Collection<? extends GrantedAuthority> getAuthorities()` — roles/permissions, e.g. `ROLE_USER`, `ROLE_ADMIN`. - Four boolean status flags: `isEnabled()`, `isAccountNonExpired()`, `isAccountNonLocked()`, `isCredentialsNonExpired()`. If any of these is false, authentication fails even with a correct password (Spring throws `DisabledException`, `AccountExpiredException`, `LockedException`, or `CredentialsExpiredException`). **Ready-made implementation:** Spring provides `org.springframework.security.core.userdetails.User` with a builder: ```java User.withUsername("alice").password(hash).roles("USER").build(); ``` **The golden rule — never return null.** If no user matches, you must throw `UsernameNotFoundException`. Returning `null` causes Spring to throw an `InternalAuthenticationServiceException` (a server-side error), not a clean authentication failure. Many developers wrap this incorrectly. **Where it fits:** During a form login, `DaoAuthenticationProvider` calls your `UserDetailsService.loadUserByUsername(...)`, then uses a `PasswordEncoder` to compare the submitted password with `userDetails.getPassword()`. If everything matches and all status flags are good, an authenticated `Authentication` is placed into the `SecurityContext`. **Common implementations you can pick from:** `InMemoryUserDetailsManager` (a Map, for demos/tests), `JdbcUserDetailsManager` (a relational schema), or a **custom** `UserDetailsService` that adapts your own domain entity — the most common choice in real apps. **Gotcha — username case and normalization:** the lookup uses whatever string arrives; if your DB is case-sensitive you may need to normalize. **Gotcha — mapping authorities:** roles must be prefixed `ROLE_` when you use `hasRole(...)`; `User.builder().roles("USER")` adds the prefix for you, while `.authorities("ROLE_USER")` does not add anything automatically. **When to use:** whenever you need username/password (form or Basic) authentication backed by your own user store. For OAuth2/OIDC login you'd instead implement `OAuth2UserService`; UserDetailsService is specifically for the DAO (username/password) flow.

  • What happens if loadUserByUsername returns null instead of throwing?
    Spring wraps it in an InternalAuthenticationServiceException — a server-side failure rather than a clean BadCredentials response. Always throw UsernameNotFoundException so the framework can handle the 'no such user' case correctly (including enumeration protection).
  • Does UserDetailsService verify the password?
    No. It only loads the user. Password comparison is done by the AuthenticationProvider (DaoAuthenticationProvider) using a PasswordEncoder against the encoded password stored in UserDetails.getPassword().

saying these in an interview costs you the question

  • Saying loadUserByUsername checks/compares the password (it only retrieves the user)
  • Returning null when the user is not found instead of throwing UsernameNotFoundException
  • Storing or returning a plaintext password in UserDetails.getPassword()
  • Confusing UserDetailsService (username/password) with OAuth2UserService (OAuth2 login)

context