DEV Community

SpringCraft
SpringCraft

Posted on

JWT vs Session vs OAuth2 in Spring Boot: Which One Should You Use?

JWT vs Session vs OAuth2 in Spring Boot: Which One Should You Use?

Tags: java springboot security webdev


If you've spent any time building APIs with Spring Boot, you've inevitably faced this question: how do I handle authentication?

You Google it. You find three different approaches. Every Stack Overflow answer recommends something different. And you end up more confused than when you started.

After 8 years building Java backends, I've used all three in production. Here's the honest breakdown — no hype, no "it depends" cop-outs.


The three approaches

Before comparing, let's make sure we're talking about the same things.

Session-based auth stores the user's state on the server. After login, the server creates a session, stores it in memory (or Redis), and sends a session ID to the client via a cookie. Every request, the server looks up that session ID to identify the user.

JWT (JSON Web Token) is stateless. After login, the server generates a signed token containing the user's identity and roles. The client stores it and sends it on every request. The server just verifies the signature — no database lookup needed.

OAuth2 is a delegation protocol. It lets your app authenticate users via a third-party provider (Google, GitHub, Keycloak, etc.) or issue tokens to other services on behalf of a user. It's not a replacement for session or JWT — it's a framework that often uses JWT under the hood.


The honest comparison

Session JWT OAuth2
State Server-side Stateless Depends on provider
Scalability Harder (shared state) Easy (no shared state) Easy
Revocation Instant Hard Depends
Complexity Low Medium High
Best for Monoliths, SSR apps REST APIs, microservices SSO, third-party auth

When to use sessions

Sessions are underrated. If you're building a monolith with server-side rendering (Thymeleaf, JSP), sessions are the right tool. They're simple, battle-tested, and Spring Security handles them beautifully out of the box.

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/admin/**").hasRole("ADMIN")
            .anyRequest().authenticated()
        )
        .formLogin(Customizer.withDefaults())
        .sessionManagement(session -> session
            .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
        );
    return http.build();
}
Enter fullscreen mode Exit fullscreen mode

The main pain point: horizontal scaling. If you run multiple instances, every instance needs access to the same session store. You'll need Redis or Spring Session to share state across nodes. Manageable, but adds infrastructure complexity.

Use sessions when:

  • You're building a monolith
  • Your frontend is server-rendered
  • You don't need to scale to dozens of instances
  • Simplicity matters more than flexibility

When to use JWT

JWT shines in REST APIs and microservices. Your backend is stateless, your frontend is a React/Angular/Vue app or a mobile client, and you need tokens that travel across services without a shared session store.

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .csrf(csrf -> csrf.disable())
        .sessionManagement(session -> session
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        )
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/auth/**").permitAll()
            .anyRequest().authenticated()
        )
        .oauth2ResourceServer(oauth2 -> oauth2
            .jwt(Customizer.withDefaults())
        );
    return http.build();
}
Enter fullscreen mode Exit fullscreen mode

The real gotcha with JWT: token revocation. Once issued, a JWT is valid until it expires. If a user logs out or you need to invalidate a token immediately (compromised account, role change), you can't — unless you maintain a blocklist, which reintroduces server-side state.

The solution most teams use: short-lived access tokens (15 min) + longer-lived refresh tokens stored server-side. Best of both worlds, but more complexity to implement correctly.

Use JWT when:

  • You're building a REST API consumed by SPAs or mobile apps
  • You have microservices that need to verify identity without a shared store
  • You need cross-domain authentication
  • Stateless scalability is a priority

When to use OAuth2

OAuth2 is not an alternative to JWT or sessions — it's a higher-level framework. You use it when:

1. You want SSO (Single Sign-On)
Users log in once and access multiple apps. Keycloak, Okta, Auth0 handle the complexity.

2. You're building a multi-tenant SaaS
Each tenant authenticates via their own identity provider. OAuth2 + OIDC is the standard.

3. You expose an API to third parties
Third-party apps need to access your API on behalf of your users. OAuth2 authorization code flow is the standard.

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .anyRequest().authenticated()
        )
        .oauth2Login(Customizer.withDefaults())
        .oauth2ResourceServer(oauth2 -> oauth2
            .jwt(Customizer.withDefaults())
        );
    return http.build();
}
Enter fullscreen mode Exit fullscreen mode

The honest warning: OAuth2 has a steep learning curve. Flows, scopes, grants, PKCE, token introspection — there's a lot to get right. If you don't need SSO or third-party delegation, JWT is simpler and covers 90% of use cases.

Use OAuth2 when:

  • You need SSO across multiple applications
  • You're integrating with external identity providers (Google, GitHub, Keycloak)
  • You expose APIs to third-party developers
  • You're building enterprise software where OIDC compliance matters

The decision flowchart

Is your frontend server-rendered (Thymeleaf, JSP)?
└── YES → Use Sessions

Is your app a REST API or SPA?
└── YES → Do you need SSO or third-party auth?
    ├── YES → Use OAuth2 (+ JWT under the hood)
    └── NO  → Use JWT
Enter fullscreen mode Exit fullscreen mode

What I actually use in production

For most projects I work on — REST APIs consumed by React frontends — the stack is:

  • JWT for stateless auth
  • Access token: 15 minutes expiry
  • Refresh token: 7 days, stored server-side (allows revocation)
  • Spring Security 6 with oauth2ResourceServer for token validation
  • PostgreSQL for refresh token storage

This covers 90% of use cases without the complexity of a full OAuth2 setup.

If the project grows to multiple apps needing SSO, I add Keycloak in front and switch to OAuth2. The Spring Boot config change is minimal — most of the work is infrastructure.


TL;DR

  • Sessions → monoliths, server-rendered apps, simplicity first
  • JWT → REST APIs, SPAs, microservices, stateless scalability
  • OAuth2 → SSO, third-party auth, enterprise, multi-tenant SaaS

There's no universally right answer. But there is a right answer for your use case — and now you have the framework to find it.


I'm currently building a production-ready Spring Boot 3 + JWT starter kit with all of this pre-configured. Drop a comment if you're interested — launching soon.

Top comments (0)