Skip to content

jwtk/jjwt

Repository files navigation

Java JWT: JSON Web Token for Java and Android

Build Status Coverage Status Vuln score Known Vulns

JJWT aims to be the easiest to use and understand library for creating and verifying JSON Web Tokens (JWTs) and JSON Web Keys (JWKs) on the JVM and Android.

JJWT is a pure Java implementation based exclusively on the JOSE Working Group RFC specifications:

It was created by Les Hazlewood and is supported and maintained by a community of contributors.

JJWT is open source under the terms of the Apache 2.0 License.

Table of Contents


Features

  • Fully functional on all Java 7+ JDKs and Android

  • Automatic security best practices and assertions

  • Easy to learn and read API

  • Convenient and readable fluent interfaces, great for IDE auto-completion to write code quickly

  • Fully RFC specification compliant on all implemented functionality, tested against RFC-specified test vectors

  • Stable implementation with almost 1,700 tests and enforced 100% test code coverage. Every single method, statement and conditional branch variant in the entire codebase is tested and required to pass on every build.

  • Creating, parsing and verifying digitally signed compact JWTs (aka JWSs) with all standard JWS algorithms:

    Identifier Signature Algorithm

    HS256

    HMAC using SHA-256

    HS384

    HMAC using SHA-384

    HS512

    HMAC using SHA-512

    ES256

    ECDSA using P-256 and SHA-256

    ES384

    ECDSA using P-384 and SHA-384

    ES512

    ECDSA using P-521 and SHA-512

    RS256

    RSASSA-PKCS-v1_5 using SHA-256

    RS384

    RSASSA-PKCS-v1_5 using SHA-384

    RS512

    RSASSA-PKCS-v1_5 using SHA-512

    PS256

    RSASSA-PSS using SHA-256 and MGF1 with SHA-2561

    PS384

    RSASSA-PSS using SHA-384 and MGF1 with SHA-3841

    PS512

    RSASSA-PSS using SHA-512 and MGF1 with SHA-5121

    EdDSA

    Edwards-curve Digital Signature Algorithm2

    1. Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath.

    2. Requires Java 15 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath.

  • Creating, parsing and decrypting encrypted compact JWTs (aka JWEs) with all standard JWE encryption algorithms:

    Identifier Encryption Algorithm

    A128CBC‑HS256

    AES_128_CBC_HMAC_SHA_256 authenticated encryption algorithm

    A192CBC-HS384

    AES_192_CBC_HMAC_SHA_384 authenticated encryption algorithm

    A256CBC-HS512

    AES_256_CBC_HMAC_SHA_512 authenticated encryption algorithm

    A128GCM

    AES GCM using 128-bit key1

    A192GCM

    AES GCM using 192-bit key1

    A256GCM

    AES GCM using 256-bit key1

    1. Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath.

  • All Key Management Algorithms for obtaining JWE encryption and decryption keys:

    Identifier Key Management Algorithm

    RSA1_5

    RSAES-PKCS1-v1_5

    RSA-OAEP

    RSAES OAEP using default parameters

    RSA-OAEP-256

    RSAES OAEP using SHA-256 and MGF1 with SHA-256

    A128KW

    AES Key Wrap with default initial value using 128-bit key

    A192KW

    AES Key Wrap with default initial value using 192-bit key

    A256KW

    AES Key Wrap with default initial value using 256-bit key

    dir

    Direct use of a shared symmetric key as the CEK

    ECDH-ES

    Elliptic Curve Diffie-Hellman Ephemeral Static key agreement using Concat KDF

    ECDH-ES+A128KW

    ECDH-ES using Concat KDF and CEK wrapped with "A128KW"

    ECDH-ES+A192KW

    ECDH-ES using Concat KDF and CEK wrapped with "A192KW"

    ECDH-ES+A256KW

    ECDH-ES using Concat KDF and CEK wrapped with "A256KW"

    A128GCMKW

    Key wrapping with AES GCM using 128-bit key1

    A192GCMKW

    Key wrapping with AES GCM using 192-bit key1

    A256GCMKW

    Key wrapping with AES GCM using 256-bit key1

    PBES2-HS256+A128KW

    PBES2 with HMAC SHA-256 and "A128KW" wrapping1

    PBES2-HS384+A192KW

    PBES2 with HMAC SHA-384 and "A192KW" wrapping1

    PBES2‑HS512+A256KW

    PBES2 with HMAC SHA-512 and "A256KW" wrapping1

    1. Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath.

  • Creating, parsing and verifying JSON Web Keys (JWKs) in all standard JWA key formats using native Java Key types:

    JWK Key Format Java Key Type JJWT Jwk Type

    Symmetric Key

    SecretKey

    SecretJwk

    Elliptic Curve Public Key

    ECPublicKey

    EcPublicJwk

    Elliptic Curve Private Key

    ECPrivateKey

    EcPrivateJwk

    RSA Public Key

    RSAPublicKey

    RsaPublicJwk

    RSA Private Key

    RSAPrivateKey

    RsaPrivateJwk

    XDH Private Key

    XECPublicKey1

    OctetPublicJwk

    XDH Private Key

    XECPrivateKey1

    OctetPrivateJwk

    EdDSA Public Key

    EdECPublicKey2

    OctetPublicJwk

    EdDSA Private Key

    EdECPublicKey2

    OctetPrivateJwk

    1. Requires Java 15 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath.

    2. Requires Java 15 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath.

  • Convenience enhancements beyond the specification such as

    • Payload compression for any large JWT, not just JWEs

    • Claims assertions (requiring specific values)

    • Claim POJO marshaling and unmarshalling when using a compatible JSON parser (e.g. Jackson)

    • Secure Key generation based on desired JWA algorithms

    • and more…​

Currently Unsupported Features

This feature may be implemented in a future release. Community contributions are welcome!

Community

Getting Help

If you have trouble using JJWT, please first read the documentation on this page before asking questions. We try very hard to ensure JJWT’s documentation is robust, categorized with a table of contents, and up to date for each release.

Questions

If the documentation or the API JavaDoc isn’t sufficient, and you either have usability questions or are confused about something, please ask your question here. However:

Please do not create a GitHub issue to ask a question.

We use GitHub Issues to track actionable work that requires changes to JJWT’s design and/or codebase. If you have a usability question, instead please ask your question here, and we can convert that to an issue if necessary.

If a GitHub Issue is created that does not represent actionable work for JJWT’s codebase, it will be promptly closed.

Bugs, Feature Requests, Ideas and General Discussions

If you do not have a usability question and believe you have a legitimate bug or feature request, please discuss it here FIRST. Please do a quick search first to see if an existing discussion related to yours exist already and join that existing discussion if necesary.

If you feel like you’d like to help fix a bug or implement the new feature yourself, please read the Contributing section next before starting any work.

Contributing

Pull Requests

Simple Pull Requests that fix anything other than JJWT core code (documentation, JavaDoc, typos, test cases, etc) are always appreciated and have a high likelihood of being merged quickly. Please send them!

However, if you want or feel the need to change JJWT’s functionality or core code, please do not issue a pull request without starting a new JJWT discussion and discussing your desired changes first, before you start working on it.

It would be a shame to reject your earnest and genuinely-appreciated pull request if it might not align with the project’s goals, design expectations or planned functionality. We’ve sadly had to reject large PRs in the past because they were out of sync with project or design expectations - all because the PR author didn’t first check in with the team first before working on a solution.

So, please create a new JJWT discussion first to discuss, and then we can see easily convert the discussion to an issue and then see if (or how) a PR is warranted. Thank you!

Help Wanted

If you would like to help, but don’t know where to start, please visit the Help Wanted Issues page and pick any of the ones there, and we’ll be happy to discuss and answer questions in the issue comments.

If any of those don’t appeal to you, no worries! Any help you would like to offer would be appreciated based on the above caveats concerning contributing pull requests. Feel free to discuss or ask questions first if you’re not sure. :)

What is a JSON Web Token?

JSON Web Token (JWT) is a general-purpose text-based messaging format for transmitting information in a compact and secure way. Contrary to popular belief, JWT is not just useful for sending and receiving identity tokens on the web - even if that is the most common use case. JWTs can be used as messages for any type of data.

A JWT in its simplest form contains two parts:

  1. The primary data within the JWT, called the payload, and

  2. A JSON Object with name/value pairs that represent metadata about the payload and the message itself, called the header.

A JWT payload can be absolutely anything at all - anything that can be represented as a byte array, such as Strings, images, documents, etc.

But because a JWT header is a JSON Object, it would make sense that a JWT payload could also be a JSON Object as well. In many cases, developers like the payload to be JSON that represents data about a user or computer or similar identity concept. When used this way, the payload is called a JSON Claims object, and each name/value pair within that object is called a claim - each piece of information within 'claims' something about an identity.

And while it is useful to 'claim' something about an identity, really anyone can do that. What’s important is that you trust the claims by verifying they come from a person or computer you trust.

A nice feature of JWTs is that they can be secured in various ways. A JWT can be cryptographically signed (making it what we call a JWS) or encrypted (making it a JWE). This adds a powerful layer of verifiability to the JWT - a JWS or JWE recipient can have a high degree of confidence it comes from someone they trust by verifying a signature or decrypting it. It is this feature of verifiability that makes JWT a good choice for sending and receiving secure information, like identity claims.

Finally, JSON with whitespace for human readability is nice, but it doesn’t make for a very efficient message format. Therefore, JWTs can be compacted (and even compressed) to a minimal representation - basically Base64URL-encoded strings - so they can be transmitted around the web more efficiently, such as in HTTP headers or URLs.

JWT Example

Once you have a payload and header, how are they compacted for web transmission, and what does the final JWT actually look like? Let’s walk through a simplified version of the process with some pseudocode:

  1. Assume we have a JWT with a JSON header and a simple text message payload:

    header

    {
      "alg": "none"
    }

    payload

    The true sign of intelligence is not knowledge but imagination.
  2. Remove all unnecessary whitespace in the JSON:

    String header = '{"alg":"none"}'
    String payload = 'The true sign of intelligence is not knowledge but imagination.'
  3. Get the UTF-8 bytes and Base64URL-encode each:

    String encodedHeader = base64URLEncode( header.getBytes("UTF-8") )
    String encodedPayload = base64URLEncode( payload.getBytes("UTF-8") )
  4. Join the encoded header and claims with period ('.') characters:

    String compact = encodedHeader + '.' + encodedPayload + '.'

The final concatenated compact JWT String looks like this:

eyJhbGciOiJub25lIn0.VGhlIHRydWUgc2lnbiBvZiBpbnRlbGxpZ2VuY2UgaXMgbm90IGtub3dsZWRnZSBidXQgaW1hZ2luYXRpb24u.

This is called an 'unprotected' JWT because no security was involved - no digital signatures or encryption to 'protect' the JWT to ensure it cannot be changed by 3rd parties.

If we wanted to digitally sign the compact form so that we could at least guarantee that no-one changes the data without us detecting it, we’d have to perform a few more steps, shown next.

JWS Example

Instead of a plain text payload, the next example will use probably the most common type of payload - a JSON claims Object containing information about a particular identity. We’ll also digitally sign the JWT to ensure it cannot be changed by a 3rd party without us knowing.

  1. Assume we have a JSON header and a claims payload:

    header

    {
      "alg": "HS256"
    }

    payload

    {
      "sub": "Joe"
    }

    In this case, the header indicates that the HS256 (HMAC using SHA-256) algorithm will be used to cryptographically sign the JWT. Also, the payload JSON object has a single claim, sub with value Joe.

    There are a number of standard claims, called Registered Claims, in the specification and sub (for 'Subject') is one of them.

  2. Remove all unnecessary whitespace in both JSON objects:

    String header = '{"alg":"HS256"}'
    String claims = '{"sub":"Joe"}'
  3. Get their UTF-8 bytes and Base64URL-encode each:

    String encodedHeader = base64URLEncode( header.getBytes("UTF-8") )
    String encodedClaims = base64URLEncode( claims.getBytes("UTF-8") )
  4. Concatenate the encoded header and claims with a period character '.' delimiter:

    String concatenated = encodedHeader + '.' + encodedClaims
  5. Use a sufficiently-strong cryptographic secret or private key, along with a signing algorithm of your choice (we’ll use HMAC-SHA-256 here), and sign the concatenated string:

     SecretKey key = getMySecretKey()
     byte[] signature = hmacSha256( concatenated, key )
  6. Because signatures are always byte arrays, Base64URL-encode the signature and join it to the concatenated string with a period character '.' delimiter:

    String compact = concatenated + '.' + base64URLEncode( signature )

And there you have it, the final compact String looks like this:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.1KP0SsvENi7Uz1oQc07aXTL7kpQG5jBNIybqr60AlD4

This is called a 'JWS' - short for signed JWT.

Of course, no one would want to do this manually in code, and worse, if you get anything wrong, you could introduce serious security problems and weaknesses. As a result, JJWT was created to handle all of this for you: JJWT completely automates both the creation of JWSs and the parsing and verification of JWSs for you.

JWE Example

So far we have seen an unprotected JWT and a cryptographically signed JWT (called a 'JWS'). One of the things that is inherent to both of these two is that all the information within them can be seen by anyone - all the data in both the header and the payload is publicly visible. JWS just ensures the data hasn’t been changed by anyone - it doesn’t prevent anyone from seeing it. Many times, this is just fine because the data within them is not sensitive information.

But what if you needed to represent information in a JWT that is considered sensitive information - maybe someone’s postal address or social security number or bank account number?

In these cases, we’d want a fully-encrypted JWT, called a 'JWE' for short. A JWE uses cryptography to ensure that the payload remains fully encrypted and authenticated so unauthorized parties cannot see data within, nor change the data without being detected. Specifically, the JWE specification requires that Authenticated Encryption with Associated Data algorithms are used to fully encrypt and protect data.

A full overview of AEAD algorithms are out of scope for this documentation, but here’s an example of a final compact JWE that utilizes these algorithms (line breaks are for readability only):

eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.
6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ.
AxY8DCtDaGlsbGljb3RoZQ.
KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY.
U0m_YmjN04DJvceFICbCVQ

Next we’ll cover how to install JJWT in your project, and then we’ll see how to use JJWT’s nice fluent API instead of risky string manipulation to quickly and safely build JWTs, JWSs, and JWEs.

Installation

Use your favorite Maven-compatible build tool to pull the dependencies from Maven Central.

The dependencies could differ slightly if you are working with a JDK project or an Android project.

JDK Projects

If you’re building a (non-Android) JDK project, you will want to define the following dependencies:

Maven

<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> <!-- or jjwt-gson if Gson is preferred -->
    <version>0.12.6</version>
    <scope>runtime</scope>
</dependency>
<!-- Uncomment this next dependency if you are using:
     - JDK 10 or earlier, and you want to use RSASSA-PSS (PS256, PS384, PS512) signature algorithms.
     - JDK 10 or earlier, and you want to use EdECDH (X25519 or X448) Elliptic Curve Diffie-Hellman encryption.
     - JDK 14 or earlier, and you want to use EdDSA (Ed25519 or Ed448) Elliptic Curve signature algorithms.
     It is unnecessary for these algorithms on JDK 15 or later.
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk18on</artifactId> or bcprov-jdk15to18 on JDK 7
    <version>1.76</version>
    <scope>runtime</scope>
</dependency>
-->

Gradle

dependencies {
    implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
    runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
    runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6' // or 'io.jsonwebtoken:jjwt-gson:0.12.6' for gson
    /*
      Uncomment this next dependency if you are using:
       - JDK 10 or earlier, and you want to use RSASSA-PSS (PS256, PS384, PS512) signature algorithms.
       - JDK 10 or earlier, and you want to use EdECDH (X25519 or X448) Elliptic Curve Diffie-Hellman encryption.
       - JDK 14 or earlier, and you want to use EdDSA (Ed25519 or Ed448) Elliptic Curve signature algorithms.
      It is unnecessary for these algorithms on JDK 15 or later.
    */
    // runtimeOnly 'org.bouncycastle:bcprov-jdk18on:1.76' // or bcprov-jdk15to18 on JDK 7
}

Android Projects

Android projects will want to define the following dependencies and Proguard exclusions, and optional BouncyCastle Provider:

Dependencies

Add the dependencies to your project:

dependencies {
    api('io.jsonwebtoken:jjwt-api:0.12.6')
    runtimeOnly('io.jsonwebtoken:jjwt-impl:0.12.6')
    runtimeOnly('io.jsonwebtoken:jjwt-orgjson:0.12.6') {
        exclude(group: 'org.json', module: 'json') //provided by Android natively
    }
    /*
      Uncomment this next dependency if you want to use:
       - RSASSA-PSS (PS256, PS384, PS512) signature algorithms.
       - EdECDH (X25519 or X448) Elliptic Curve Diffie-Hellman encryption.
       - EdDSA (Ed25519 or Ed448) Elliptic Curve signature algorithms.
      ** AND ALSO ensure you enable the BouncyCastle provider as shown below **
    */
    //implementation('org.bouncycastle:bcprov-jdk18on:1.76') // or bcprov-jdk15to18 for JDK 7
}

Proguard

You can use the following Android Proguard exclusion rules:

-keepattributes InnerClasses

-keep class io.jsonwebtoken.** { *; }
-keepnames class io.jsonwebtoken.* { *; }
-keepnames interface io.jsonwebtoken.* { *; }

-keep class org.bouncycastle.** { *; }
-keepnames class org.bouncycastle.** { *; }
-dontwarn org.bouncycastle.**

Bouncy Castle

If you want to use JWT RSASSA-PSS algorithms (i.e. PS256, PS384, and PS512), EdECDH (X25512 or X448) Elliptic Curve Diffie-Hellman encryption, EdDSA (Ed25519 or Ed448) signature algorithms, or you just want to ensure your Android application is running an updated version of BouncyCastle, you will need to:

  1. Uncomment the BouncyCastle dependency as commented above in the dependencies section.

  2. Replace the legacy Android custom BC provider with the updated one.

Provider registration needs to be done early in the application’s lifecycle, preferably in your application’s main Activity class as a static initialization block. For example:

class MainActivity : AppCompatActivity() {

    companion object {
        init {
            Security.removeProvider("BC") //remove old/legacy Android-provided BC provider
            Security.addProvider(BouncyCastleProvider()) // add 'real'/correct BC provider
        }
    }

    // ... etc ...
}

Understanding JJWT Dependencies

Notice the above JJWT dependency declarations all have only one compile-time dependency and the rest are declared as runtime dependencies.

This is because JJWT is designed so you only depend on the APIs that are explicitly designed for you to use in your applications and all other internal implementation details - that can change without warning - are relegated to runtime-only dependencies. This is an extremely important point if you want to ensure stable JJWT usage and upgrades over time:

⚠️WARNING

JJWT guarantees semantic versioning compatibility for all of its artifacts except the jjwt-impl .jar. No such guarantee is made for the jjwt-impl .jar and internal changes in that .jar can happen at any time. Never add the jjwt-impl .jar to your project with compile scope - always declare it with runtime scope.

This is done to benefit you: great care goes into curating the jjwt-api .jar and ensuring it contains what you need and remains backwards compatible as much as is possible so you can depend on that safely with compile scope. The runtime jjwt-impl .jar strategy affords the JJWT developers the flexibility to change the internal packages and implementations whenever and however necessary. This helps us implement features, fix bugs, and ship new releases to you more quickly and efficiently.

Quickstart

Most complexity is hidden behind a convenient and readable builder-based fluent interface, great for relying on IDE auto-completion to write code quickly. Here’s an example:

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import java.security.Key;

// We need a signing key, so we'll create one just for this example. Usually
// the key would be read from your application configuration instead.
SecretKey key = Jwts.SIG.HS256.key().build();

String jws = Jwts.builder().subject("Joe").signWith(key).compact();

How easy was that!?

In this case, we are:

  1. building a JWT that will have the registered claim sub (Subject) set to Joe. We are then

  2. signing the JWT using a key suitable for the HMAC-SHA-256 algorithm. Finally, we are

  3. compacting it into its final String form. A signed JWT is called a 'JWS'.

The resultant jws String looks like this:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.1KP0SsvENi7Uz1oQc07aXTL7kpQG5jBNIybqr60AlD4

Now let’s verify the JWT (you should always discard JWTs that don’t match an expected signature):

assert Jwts.parser().verifyWith(key).build().parseSignedClaims(jws).getPayload().getSubject().equals("Joe");

There are two things going on here. The key from before is being used to verify the signature of the JWT. If it fails to verify the JWT, a SignatureException (which extends JwtException) is thrown. Assuming the JWT is verified, we parse the claims and assert that that subject is set to Joe. You have to love code one-liners that pack a punch!

ℹ️ NOTE

Type-safe JWTs: To get a type-safe Claims JWT result, call the parseSignedClaims method (since there are many similar methods available). You will get an UnsupportedJwtException if you parse your JWT with wrong method.

But what if parsing or signature validation failed? You can catch JwtException and react accordingly:

try {

    Jwts.parser().verifyWith(key).build().parseSignedClaims(compactJws);

    //OK, we can trust this JWT

} catch (JwtException e) {

    //don't trust the JWT!
}

Now that we’ve had a quickstart 'taste' of how to create and parse JWTs, let’s cover JJWT’s API in-depth.

Creating a JWT

You create a JWT as follows:

  1. Use the Jwts.builder() method to create a JwtBuilder instance.

  2. Optionally set any header parameters as desired.

  3. Call builder methods to set the payload content or claims.

  4. Optionally call signWith or encryptWith methods if you want to digitally sign or encrypt the JWT.

  5. Call the compact() method to produce the resulting compact JWT string.

For example:

String jwt = Jwts.builder()                     // (1)

    .header()                                   // (2) optional
        .keyId("aKeyId")
        .and()

    .subject("Bob")                             // (3) JSON Claims, or
    //.content(aByteArray, "text/plain")        //     any byte[] content, with media type

    .signWith(signingKey)                       // (4) if signing, or
    //.encryptWith(key, keyAlg, encryptionAlg)  //     if encrypting

    .compact();                                 // (5)
  • The JWT payload may be either byte[] content (via content) or JSON Claims (such as subject, claims, etc), but not both.

  • Either digital signatures (signWith) or encryption (encryptWith) may be used, but not both.

⚠️WARNING

Unprotected JWTs: If you do not use the signWith or encryptWith builder methods, an Unprotected JWT will be created, which offers no security protection at all. If you need security protection, consider either digitally signing or encrypting the JWT before calling the compact() builder method.

JWT Header

A JWT header is a JSON Object that provides metadata about the contents, format, and any cryptographic operations relevant to the JWT payload. JJWT provides a number of ways of setting the entire header and/or multiple individual header parameters (name/value pairs).

JwtBuilder Header

The easiest and recommended way to set one or more JWT header parameters (name/value pairs) is to use the JwtBuilder's header() builder as desired, and then call its and() method to return back to the JwtBuilder for further configuration. For example:

String jwt = Jwts.builder()

    .header()                        // <----
        .keyId("aKeyId")
        .x509Url(aUri)
        .add("someName", anyValue)
        .add(mapValues)
        // ... etc ...
        .and()                      // go back to the JwtBuilder

    .subject("Joe")                 // resume JwtBuilder calls...
    // ... etc ...
    .compact();

The JwtBuilder header() builder also supports automatically calculating X.509 thumbprints and other builder-style benefits that a simple property getter/setter object would not do.

ℹ️ NOTE

Automatic Headers: You do not need to set the alg, enc or zip headers - JJWT will always set them automatically as needed.