Blog
Wild & Free Tools

How to Decode a JWT in Flutter and Android Without a Library

Last updated: March 2026 4 min read
Quick Answer

Table of Contents

  1. Flutter / Dart
  2. Android — Kotlin
  3. Android — Java
  4. Checking Expiry in Mobile
  5. Frequently Asked Questions

Decoding a JWT in a mobile app uses the same base64url approach as the browser — split on dots, decode the middle segment, parse JSON. Both Flutter/Dart and Android (Kotlin or Java) have built-in utilities that make this straightforward without adding any package.

Decode JWT in Flutter and Dart

Flutter includes dart:convert for base64 decoding and JSON parsing. No pub.dev package needed:

import 'dart:convert';

Map<String, dynamic> decodeJwtPayload(String token) {
  final parts = token.split('.');
  if (parts.length != 3) throw Exception('Invalid JWT format');

  String payload = parts[1];

  // Normalize base64url to base64 and add padding
  payload = payload.replaceAll('-', '+').replaceAll('_', '/');
  while (payload.length % 4 != 0) {
    payload += '=';
  }

  final decoded = utf8.decode(base64Decode(payload));
  return jsonDecode(decoded) as Map<String, dynamic>;
}

// Usage
final claims = decodeJwtPayload(accessToken);
final userId = claims['sub'] as String;
final exp = claims['exp'] as int;
final expDate = DateTime.fromMillisecondsSinceEpoch(exp * 1000);
print('Token expires: $expDate');

The while (payload.length % 4 != 0) loop handles the variable padding that base64url strips.

Decode JWT in Android with Kotlin

import android.util.Base64
import org.json.JSONObject

fun decodeJwtPayload(token: String): JSONObject {
    val parts = token.split(".")
    require(parts.size == 3) { "Invalid JWT format" }

    var payload = parts[1]
        .replace("-", "+")
        .replace("_", "/")

    // Add padding
    when (payload.length % 4) {
        2 -> payload += "=="
        3 -> payload += "="
    }

    val decoded = Base64.decode(payload, Base64.DEFAULT)
    return JSONObject(String(decoded, Charsets.UTF_8))
}

// Usage
val claims = decodeJwtPayload(accessToken)
val userId = claims.getString("sub")
val exp = claims.getLong("exp")
val expDate = java.util.Date(exp * 1000L)

Android's android.util.Base64 handles standard base64 decoding after the character substitution.

Sell Custom Apparel — We Handle Printing & Free Shipping

Decode JWT in Android with Java

import android.util.Base64;
import org.json.JSONObject;

public static JSONObject decodeJwtPayload(String token) throws Exception {
    String[] parts = token.split("\.");
    if (parts.length != 3) throw new IllegalArgumentException("Invalid JWT");

    String payload = parts[1]
        .replace("-", "+")
        .replace("_", "/");

    // Add padding
    int mod = payload.length() % 4;
    if (mod == 2) payload += "==";
    else if (mod == 3) payload += "=";

    byte[] decoded = Base64.decode(payload, Base64.DEFAULT);
    return new JSONObject(new String(decoded, "UTF-8"));
}

// Usage
JSONObject claims = decodeJwtPayload(accessToken);
String userId = claims.getString("sub");
long exp = claims.getLong("exp");

Checking Token Expiry Before API Calls

In mobile apps, checking expiry before an API call prevents unnecessary 401 errors:

// Kotlin helper
fun isJwtExpired(token: String): Boolean {
    return try {
        val claims = decodeJwtPayload(token)
        val exp = claims.optLong("exp", 0L)
        if (exp == 0L) return false  // no expiry claim
        val nowSeconds = System.currentTimeMillis() / 1000L
        nowSeconds > exp
    } catch (e: Exception) {
        true  // treat malformed tokens as expired
    }
}

// Before making a request
if (isJwtExpired(accessToken)) {
    accessToken = refreshTokens()
}
apiService.getData(accessToken)

Add a buffer (e.g., nowSeconds > exp - 60) to refresh slightly before expiry, avoiding race conditions where a token expires mid-request.

Test Your Token in the Browser First

Paste your JWT above to inspect all claims before implementing the mobile decoder — verify the payload shape first.

Open Free JWT Decoder

Frequently Asked Questions

Should I use a JWT library for Flutter like dart_jsonwebtoken?

If you only need to decode (not verify or sign), the built-in approach works fine and keeps your dependency count low. dart_jsonwebtoken is worth adding if you need signature verification or JWT generation.

Can I decode JWTs in React Native the same way?

Yes. React Native JavaScript runs in a JS engine that supports atob() (via polyfill or directly on newer versions). The same decodeJwt() function from the JavaScript post works in React Native with a base64 polyfill if needed.

Is it safe to store JWTs in Flutter SharedPreferences?

SharedPreferences on Android stores data in plaintext XML files. For access tokens, use FlutterSecureStorage instead, which uses Android Keystore and iOS Keychain for encrypted storage.

David Rosenberg
David Rosenberg Technical Writer

David spent ten years as a software developer before shifting to technical writing covering developer productivity tools.

More articles by David →
Launch Your Own Clothing Brand — No Inventory, No Risk