Zero-Dependency JWT Decoding in Java
Before adding a library, note that Java has everything it needs in the standard library to decode a JWT payload. Verification requires cryptography; decoding requires only Base64URL and JSON parsing.
import java.util.Base64;
import java.nio.charset.StandardCharsets;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;
public class JwtDecoder {
public static Map<String, Object> decodePayload(String token) throws Exception {
String[] parts = token.split("\\.");
if (parts.length != 3) throw new IllegalArgumentException("Not a JWT");
String payload = new String(
Base64.getUrlDecoder().decode(parts[1]),
StandardCharsets.UTF_8
);
return new ObjectMapper().readValue(payload, Map.class);
}
}This works in Java 8+ and is useful for logging, debugging, and building lightweight developer tools. It does not verify anything — use only for inspection.
Using jjwt (io.jsonwebtoken) — The Modern Choice
The jjwt library is the most idiomatic Java JWT library. Fluent builder API, comprehensive claim validators, and clean exception hierarchy.
Maven Dependencies
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>Verify with HS256 Secret
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
byte[] secretBytes = System.getenv("JWT_SECRET").getBytes(StandardCharsets.UTF_8);
SecretKey key = Keys.hmacShaKeyFor(secretBytes); // Must be >= 32 bytes for HS256
try {
Claims claims = Jwts.parserBuilder()
.verifyWith(key)
.requireIssuer("https://auth.example.com")
.requireAudience("https://api.example.com")
.build()
.parseSignedClaims(token)
.getPayload();
String userId = claims.getSubject();
Date expiresAt = claims.getExpiration();
List<String> roles = claims.get("roles", List.class);
} catch (ExpiredJwtException e) {
// exp is in the past
} catch (SignatureException e) {
// Signature mismatch — key wrong or token tampered
} catch (JwtException e) {
// Any other JWT error (malformed, wrong iss/aud, etc.)
}Verify with RS256 and JWKS
// Add: com.auth0:jwks-rsa:0.22.1
import com.auth0.jwk.JwkProvider;
import com.auth0.jwk.JwkProviderBuilder;
import com.auth0.jwk.Jwk;
import java.security.interfaces.RSAPublicKey;
import java.util.concurrent.TimeUnit;
JwkProvider jwks = new JwkProviderBuilder("https://auth.example.com/.well-known/jwks.json")
.cached(10, 24, TimeUnit.HOURS)
.rateLimited(10, 1, TimeUnit.MINUTES)
.build();
// Peek the header to get kid
String[] parts = token.split("\\.");
Map<String,Object> header = new ObjectMapper().readValue(
Base64.getUrlDecoder().decode(parts[0]), Map.class
);
String kid = (String) header.get("kid");
Jwk jwk = jwks.get(kid);
RSAPublicKey publicKey = (RSAPublicKey) jwk.getPublicKey();
Claims claims = Jwts.parserBuilder()
.verifyWith(publicKey)
.build()
.parseSignedClaims(token)
.getPayload();Using Auth0 java-jwt
Alternative library — more procedural style, first-class Auth0 support.
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
Algorithm algorithm = Algorithm.HMAC256(System.getenv("JWT_SECRET"));
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("https://auth.example.com")
.withAudience("https://api.example.com")
.acceptLeeway(30) // 30-second clock drift tolerance
.build();
DecodedJWT jwt = verifier.verify(token);
String userId = jwt.getSubject();
String email = jwt.getClaim("email").asString();
List<String> roles = jwt.getClaim("roles").asList(String.class);Spring Boot 3 Resource Server (Recommended for Spring Apps)
If you are building a Spring Boot API, do not roll your own JWT parsing. Spring Security has production-grade JWT validation built in.
1. Add the dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>2. Configure the issuer
# application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://auth.example.com
audiences: [https://api.example.com]3. Secure endpoints
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/health", "/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasAuthority("SCOPE_admin")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
return http.build();
}
}4. Access claims in controllers
@RestController
public class MeController {
@GetMapping("/api/me")
public Map<String, Object> me(@AuthenticationPrincipal Jwt jwt) {
return Map.of(
"userId", jwt.getSubject(),
"email", jwt.getClaimAsString("email"),
"scopes", jwt.getClaimAsStringList("scope"),
"expiresAt", jwt.getExpiresAt()
);
}
}Common Java JWT Exceptions
- ExpiredJwtException (jjwt) / TokenExpiredException (java-jwt) —
expclaim is in the past. Client must refresh. - SignatureException — signature does not match. Check that your secret/public key matches the one used to sign.
- MalformedJwtException — token is not three dot-separated Base64URL segments.
- InvalidKeyException (jjwt) — HS256 secret is shorter than 256 bits. Use a 32+ character random string.
- MissingClaimException — a required claim (like
iss) is missing. Check the token was issued by the right authority. - IncorrectClaimException — a claim exists but does not match (wrong
issoraud).
Key Facts
- Recommended lib:
- io.jsonwebtoken:jjwt 0.12.x (or Spring Security for Spring apps)
- HS256 secret:
- Must be at least 32 bytes (256 bits) — jjwt enforces this
- RS256 key:
- RSA 2048+ bits. Load from PEM with KeyFactory or fetch from JWKS.
- JWKS lib:
- com.auth0:jwks-rsa (cached, rate-limited fetch)
- Spring Boot:
- spring-boot-starter-oauth2-resource-server + issuer-uri property
Related JWT Tools
- JWT Decoder Online — decode any token instantly
- JWT Decoder Node.js — jsonwebtoken and jose patterns
- JWT Decoder Python — PyJWT for Flask/Django/FastAPI
- Verify JWT Signature — HS256/RS256 verification
- JWT RS256 Decoder — public-key verification walkthrough