skip to content

Compare InMemoryUserDetailsManager, JdbcUserDetailsManager, and a custom UserDetailsService. When would you use each?

level: middleimportance: should knowfreq 55%

answer

  1. InMemory = Map, tests/demos, non-persistent
  2. Jdbc = fixed users/authorities schema, configurable queries
  3. custom = adapt your own entity, most common
  4. managers add CRUD (UserDetailsManager)
  5. all need a PasswordEncoder

basics

~20 s

InMemoryUserDetailsManager stores users in a Map — good for demos and tests. JdbcUserDetailsManager persists users in a relational schema using a fixed default table layout. A custom UserDetailsService adapts your own user entity/store — the usual choice for real apps with domain-specific user data.

solid answer

~40 s

All three are UserDetailsService implementations. InMemoryUserDetailsManager keeps users in an in-memory Map — zero persistence, ideal for tests, demos, and small fixed user sets; users vanish on restart. JdbcUserDetailsManager reads/writes a relational store via JdbcTemplate using Spring's default schema (users and authorities tables) — usable out of the box if you adopt that schema, and it implements UserDetailsManager so it supports createUser/updateUser/changePassword. A custom UserDetailsService is what most production apps use: you implement loadUserByUsername to map your own domain entity (with fields Spring's schema doesn't have — email, tenant, MFA state) to a UserDetails. InMemory and Jdbc managers also implement UserDetailsManager (CRUD) and often GroupManager/UserDetailsPasswordService; a plain custom UserDetailsService need only implement the single load method. Choose custom when your user model diverges from the canned schema, which is almost always.

code

java · 12 lines
java
// JdbcUserDetailsManager using Spring's default schema
@Bean
JdbcUserDetailsManager jdbcUsers(DataSource ds, PasswordEncoder enc) {
    JdbcUserDetailsManager m = new JdbcUserDetailsManager(ds);
    // optional: adapt to a non-default column layout
    // m.setUsersByUsernameQuery("select login, pwd, active from account where login = ?");
    if (!m.userExists("alice")) {
        m.createUser(User.withUsername("alice")
            .password(enc.encode("pw")).roles("USER").build());
    }
    return m;
}

go deeper

for a junior

Know the three options and that InMemory is for tests/demos.

for a middle

Explain the default Jdbc schema, the UserDetailsManager CRUD extension, and why custom is common.

for a senior

Discuss configuring Jdbc queries, mapping a rich entity, and the encoder requirement for provisioning.

for a principal

Weigh schema rigidity vs domain fit, multi-tenant/authority derivation, and hash-upgrade support via UserDetailsPasswordService.

All three ultimately implement **`UserDetailsService`** (the `loadUserByUsername` contract). They differ in *where the user data lives* and *how much CRUD/management they offer*. **1) `InMemoryUserDetailsManager`** (`org.springframework.security.provisioning`) - Stores `UserDetails` in a `ConcurrentHashMap` keyed by (lowercased) username. - Implements `UserDetailsManager` (adds `createUser`, `updateUser`, `deleteUser`, `changePassword`, `userExists`) and `UserDetailsPasswordService`. - **Use for:** demos, unit/integration tests, tiny apps with a fixed, code-defined set of users, or a bootstrap admin account. Also what Spring Boot creates by default (a single `user` with a generated password) when you have no other `UserDetailsService`. - **Limits:** not persistent (state lost on restart), not shared across instances, unsuitable for real user management at scale. ```java @Bean InMemoryUserDetailsManager users(PasswordEncoder enc) { UserDetails u = User.withUsername("admin") .password(enc.encode("secret")).roles("ADMIN").build(); return new InMemoryUserDetailsManager(u); } ``` **2) `JdbcUserDetailsManager`** (`org.springframework.security.provisioning`) - Backs users with a relational DB via a `JdbcTemplate`/`DataSource`. - Uses a **default schema**: a `users` table (`username`, `password`, `enabled`) and an `authorities` table (`username`, `authority`), optionally `groups`/`group_authorities`/`group_members` for group support. Spring ships this DDL (`users.ddl` in the framework and `JdbcDaoImpl.DEFAULT_USER_BY_USERNAME_QUERY` etc.). - The queries are **configurable**: `setUsersByUsernameQuery`, `setAuthoritiesByUsernameQuery`, `setGroupAuthoritiesByUsernameQuery`, so you can point at slightly different columns. - Implements `UserDetailsManager` and `GroupManager`, giving full CRUD + group management. - **Use for:** apps that are happy to adopt (or lightly adapt) the canonical schema and want ready-made user/group management without writing repositories. - **Limits:** rigid schema; awkward if your user table has many extra columns, non-string PKs, joins, or a different naming convention. You end up overriding all the queries and it becomes brittle. **3) Custom `UserDetailsService`** - You write `loadUserByUsername` yourself, querying your own repository (JPA entity, Mongo, an external identity API) and mapping the result to a `UserDetails` (often via `User.builder()` or your own `UserDetails` implementation). - Implement only the single retrieval method; you get full freedom over the storage, extra fields, authority derivation, tenant scoping, soft-delete → disabled, etc. - Optionally also implement `UserDetailsPasswordService` (for automatic hash upgrades) or `UserDetailsManager` (if you want CRUD through the same interface), but neither is required. - **Use for:** essentially every real application whose user model is richer than username/password/enabled — which is the norm. **Decision guide:** - Test/demo/bootstrap → **InMemory**. - You accept the canonical schema and want free CRUD/groups → **Jdbc**. - Your domain owns the user model → **custom** (most common). **Gotchas:** - All three still need a `PasswordEncoder`; the *manager* variants store encoded passwords, so encode before `createUser`. - `JdbcUserDetailsManager`'s default `enabled` column is a **boolean/char** depending on DB; schema mismatches cause runtime SQL errors. - Defining any `UserDetailsService` bean **disables** Boot's default generated user — expected but a frequent 'why did my auto password stop working' surprise. - `UserDetailsManager` extends `UserDetailsService`, so a manager can be injected wherever a `UserDetailsService` is expected.

  • What extra capability does UserDetailsManager give over UserDetailsService?
    UserDetailsManager extends UserDetailsService and adds user provisioning/CRUD: createUser, updateUser, deleteUser, changePassword, and userExists. InMemoryUserDetailsManager and JdbcUserDetailsManager implement it; a plain custom UserDetailsService need not.
  • Why do most production apps write a custom UserDetailsService rather than use JdbcUserDetailsManager?
    Real user entities carry fields the default schema lacks (email, tenant, MFA, profile), use their own table/column names and PK types, and are already modeled as JPA entities. Mapping the entity in loadUserByUsername is simpler than overriding every JdbcUserDetailsManager query.

saying these in an interview costs you the question

  • Thinking InMemoryUserDetailsManager persists across restarts
  • Believing JdbcUserDetailsManager works with any arbitrary table without configuring queries
  • Not realizing all three implement UserDetailsService (are interchangeable at the provider)
  • Assuming a custom UserDetailsService must implement CRUD (it only needs loadUserByUsername)

context