What is UserDetailsService in Spring Security, and what does loadUserByUsername return?
answer
- one method: loadUserByUsername
- returns UserDetails, not a boolean
- throw UsernameNotFoundException, never return null
- carries encoded password + authorities + 4 flags
- retrieval only, no password check
basics
~20 sUserDetailsService 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 sUserDetailsService 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 linesimport 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
Know the single method, that it returns UserDetails, and that it throws UsernameNotFoundException.
Explain the four status flags and the never-return-null rule, and that password checks happen elsewhere.
Discuss adapting a domain entity to UserDetails, authority/role prefixing, and case normalization.
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)