Almost every beginner tutorial does this:
public class UserEntity implements UserDetailsAt first, it feels convenient.
Your entity already has:
- password
- role
along with below:
- displayName
- mobileNumber
So directly implementing UserDetails seems natural. But after trying to understand Spring Security internally instead of just memorizing configurations, I realized something important:
The real question is not:
"Can the entity implement
UserDetails?"
The real question is:
"Should my database model become my security model?"
That changed my entire perspective on Spring Security architecture.

The Hidden Problem with Direct Entity Implementation
When UserEntity directly implements UserDetails, One class suddenly gets too many responsibilities.
Example:
@Entity
public class UserEntity implements UserDetails
{
private String email;
private String password;
private String role;
}Now this single class becomes:
- Database persistence model
- Business/domain object
- Spring Security principal
- Authentication contract implementation
Everything is mixed. This creates tight coupling between:
- persistence layer
- security layer
- framework contracts
And over time, that becomes difficult to maintain.
What Spring Security Actually Needs
Spring Security does NOT care about:
- JPA
- database tables
- entity relationships
- business logic
It only needs a standard security contract.
That contract is:
UserDetailsWhy?
Because Spring Security internally only wants to ask questions like:
getUsername()
getPassword()
getAuthorities()
isEnabled()That's it. Spring Security does not need the full database entity. This is a very important abstraction boundary.
Better Design: Separate Entity and Security Principal
Instead of directly implementing UserDetails in the entity, we can separate concerns properly.
Architecture:
UserEntity
->
CustomUserPrincipal implements UserDetailsNow each class has a single responsibility.
UserEntity
@Entity
public class UserEntity {
private Long id;
private String email;
private String password;
private String role;
}This class now focuses only on:
- persistence
- business data
- domain representation
No Spring Security logic.
CustomUserPrincipal
public class CustomUserPrincipal
implements UserDetails {
private final UserEntity user;
public CustomUserPrincipal(UserEntity user) {
this.user = user;
}
@Override
public String getUsername() {
return user.getEmail();
}
@Override
public String getPassword() {
return user.getPassword();
}
}This class acts as:
- wrapper
- adapter
- security-specific representation
Now Spring Security interacts only with this object.
The Most Important Runtime Flow
Inside loadUserByUsername():
@Override
public UserDetails loadUserByUsername(String email) {
UserEntity user =
repo.findByEmail(email);
return new CustomUserPrincipal(user);
}This line:
new CustomUserPrincipal(user)is extremely important.
This is where:
- business object becomes
- Spring Security compatible principal
This is not "Spring magic." This is pure object-oriented design.
Why This Design Is Cleaner
Separating the classes gives multiple advantages:
1. Better Separation of Concerns
ClassResponsibilityUserEntityDatabase/business modelCustomUserPrincipalSecurity representation
2. Less Framework Coupling
Your entity no longer depends on Spring Security interfaces.
This keeps the domain layer cleaner.
3. Easier Security Evolution
Later, authentication may come from:
- JWT
- OAuth2
- LDAP
- external auth service
Your persistence model should not be tightly coupled to one security implementation.
4. Safer Design
Entities often contain:
- audit fields
- internal flags
- sensitive business data
The security principal should expose only what is needed for authentication.
Frameworks become much easier once we stop seeing annotations and start seeing software design patterns behind them.
If you found this helpful, do support with your reactions and share your thoughts in the comments.
#SpringBoot #SpringSecurity