Aplikasi berbasis JVM yang berjalan normal sering kali mengalami HTTP 401 Unauthorized atau HTTP 500 Internal Server Error begitu dikompilasi menjadi binary native menggunakan GraalVM. Masalah utama biasanya berakar pada auth token deserialization failure saat library parsing JWT/OAuth2 (seperti Jackson, Nimbus JOSE, atau JJWT) gagal merekonstruksi payload JSON ke POJO/Record karena ketiadaan metadata refleksi.

Penyebab Auth Token Deserialization Failure di Native Image

GraalVM menerapkan teknik ahead-of-time (AOT) compilation dengan analisis closed-world assumption. SubstrateVM menganalisis dependensi bytecode yang reachable dari main() entry point. Komponen yang dipanggil dinamis via Java Reflection dianggap tidak digunakan (dead code), sehingga dipotong (pruned) dari binary untuk meminimalkan ukuran file.

Ketika client mengirim Bearer token ke API gateway atau auth filter:

  1. Filter membaca header Authorization: Bearer <token> dan mengekstrak claims payload.
  2. Library JSON parser mencoba memanggil default constructor atau getter/setter via reflection untuk mengisi field seperti sub, roles, exp, atau custom claims.
  3. Karena class mapping token payload tidak didaftarkan secara eksplisit ke reachability metadata, GraalVM membuang metadata refleksi tersebut.
  4. Runtime melempar ClassNotFoundException, NoSuchMethodException, atau InvalidDefinitionException, menggagalkan pipeline otentikasi.

Identifikasi Error Integrasi Auth Gateway

Saat dynamic pruning terjadi, log native image menghasilkan stack trace khas berikut:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.auth.dto.UserTokenPayload` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (String)"{"sub":"usr_1029","roles":["ADMIN"],"exp":1741520000}"; line: 1, column: 2]
    at com.example.auth.filter.JwtAuthFilter.doFilter(JwtAuthFilter.java:48)
    at org.graalvm.nativeimage.builder/...

API gateway menerima token valid, namun microservice backend menolaknya karena instansiasi payload gagal.

Solusi: Konfigurasi Reachability Metadata (reflect-config.json)

Untuk mempertahankan metadata yang dibutuhkan runtime JSON mapper, daftarkan DTO token ke dalam META-INF/native-image/reflect-config.json.

1. Model Data Auth Payload

Gunakan Java Record atau POJO standar:

package com.example.auth.dto;

import java.util.List;

public record UserTokenPayload(
    String sub,
    List<String> roles,
    long exp
) {}

2. Entry pada reflect-config.json

Daftarkan class beserta accessors dan konstruktor secara eksplisit:

[
  {
    "name": "com.example.auth.dto.UserTokenPayload",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true,
    "allDeclaredFields": true
  }
]

Letakkan file ini di path: src/main/resources/META-INF/native-image/<group-id>/<artifact-id>/reflect-config.json.

Otomasi Metadata via GraalVM Tracing Agent

Menulis konfigurasi manual rawan kesalahan bila payload token memuat class bersarang (nested DTOs). Gunakan GraalVM Tracing Agent pada fase JVM test suite untuk mengumpulkan metadata secara otomatis.

Jalankan unit/integration test dengan JVM flag:

java -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image \
     -jar target/auth-service-runner.jar

Pastikan test case mencakup simulasi full request authentication dengan parsing token riil agar jalur eksekusi refleksi terekam oleh agent.

Pertimbangan Size Optimization

Menambahkan metadata refleksi memperbesar binary akhir. Terapkan strategi berikut untuk menjaga binary tetap optimal:

  • Hindari wildcard registration: Jangan mendaftarkan seluruh package DTO jika hanya subset class yang dipakai untuk auth payload.
  • Gunakan flag native-image terarah: Pasang parameter optimasi seperti -O3 atau optimasi ukuran -Os tanpa menonaktifkan --no-fallback.
  • Gunakan static token decoder alternatif: Bila memungkinkan, gunakan library parser berbasis bytecode generator tanpa reflection saat compile-time (misal: parsing berbasis Jackson Afterburner/Blackbird atau zero-reflection parser manual).

Pemeriksaan Akhir

Sebelum deployment ke production cluster, validasi native executable dengan smoke test spesifik auth:

./target/auth-service &
APP_PID=$!

# Kirim request dengan valid signed JWT
curl -s -o /dev/null -w "%{http_code}" \
  -H "Authorization: Bearer <VALID_TEST_JWT>" \
  http://localhost:8080/api/v1/protected-resource

kill $APP_PID

Jika output menghasilkan HTTP 200 (bukan 401 atau 500), metadata refleksi deserialization payload berhasil dipreservasi.