Menggeser aplikasi dari Java 8 atau 11 ke Java 25 bukan loncatan buta: sejak 1996 kompatibilitas menjadi janji resmi platform, dan jutaan baris kode lawas tetap berjalan di JVM modern tanpa recompile. Meski begitu, “hampir selalu kompatibel” tidak sama dengan “selalu”: API usang dihapus bertahap, perilaku bawaan bisa disesuaikan, dan kode yang menyalahgunakan bagian internal JDK bisa patah di versi baru. Chapter penutup ini merangkum model kompatibilitas Java, jadwal dukungan LTS untuk merencanakan upgrade, cara menyasar versi lama dari JDK baru, serta alat bantu yang memindai API usang sebelum menjadi masalah.

Tiga Jenis Kompatibilitas #

Dokumen migrasi resmi Oracle membedakan tiga lapis janji. Kompatibilitas biner adalah yang terkuat: class file hasil kompilasi versi lama tetap berjalan di JVM baru — JVM membaca class file lama, bukan sebaliknya. Kompatibilitas sumber menjamin kode sumber lawas tetap terkompilasi oleh javac baru; tipenya lebih lemah — kata kunci baru bisa bentrok dengan identifier lama, seperti enum yang mematahkan kode lawas saat masuk Java 5, alasan kata kunci modern seperti record dan sealed sengaja dibuat kontekstual. Kompatibilitas perilaku adalah yang paling longgar: hasil eksekusi bisa disesuaikan untuk membenahi cacat — contohnya String yang sejak Java 9 menyimpan byte alih-alih char, menghemat memori tanpa mengubah semantik. Upgrade yang tertib memeriksa ketiganya, bukan hanya yang pertama.

Tonggak LTS dan Jadwal Dukungan #

Versi LTSRilisDukungan diperpanjang (Oracle)
Java 8Maret 2014Desember 2030
Java 11September 2018Januari 2032
Java 17September 2021September 2029
Java 21September 2023September 2031
Java 25September 2025September 2033

LTS berikutnya direncanakan September 2027 (Java 29). Pola yang sehat untuk aplikasi berumur panjang: menetap di LTS, lalu berpindah ke LTS berikutnya jauh sebelum dukungan yang lama berakhir — jarak dua tahun antar LTS cukup untuk satu siklus upgrade yang tenang.

Runtime menyediakan Runtime.Version untuk mengambil versi secara pasti, dan reflection dapat mendeteksi API yang sudah berubah nasib. Contoh teruji berikut memeriksa tonggak versi serta status tiga API legendaris:

java CekMigrasi.java
public class CekMigrasi {

    // Deteksi API lama lewat reflection: ada, dihapus, atau dinonaktifkan?
    static boolean methodAda(Class<?> kelas, String nama) {
        try {
            kelas.getMethod(nama);
            return true;
        } catch (NoSuchMethodException e) {
            return false;
        }
    }

    public static void main(String[] args) {
        var versi = Runtime.version();
        System.out.println("runtime  : Java " + versi.feature()
                + " build " + versi.build().orElse(-1));

        int[] tonggak = {8, 11, 17, 21, 25};
        for (int v : tonggak) {
            String status = versi.feature() >= v ? "sudah terlewati" : "belum tercapai";
            System.out.printf("tonggak %2d : %s%n", v, status);
        }

        System.out.println("Thread.suspend  : "
                + (methodAda(Thread.class, "suspend") ? "ada" : "sudah dihapus"));
        System.out.println("Thread.stop     : "
                + (methodAda(Thread.class, "stop") ? "ada, kini selalu error" : "sudah dihapus"));

        // SecurityManager dinonaktifkan permanen sejak Java 24 (JEP 486).
        try {
            System.setSecurityManager(null);
        } catch (UnsupportedOperationException e) {
            System.out.println("SecurityManager : dinonaktifkan permanen");
        }
    }
}

Output yang Dihasilkan:

text
runtime  : Java 25 build 1
tonggak  8 : sudah terlewati
tonggak 11 : sudah terlewati
tonggak 17 : sudah terlewati
tonggak 21 : sudah terlewati
tonggak 25 : sudah terlewati
Thread.suspend  : sudah dihapus
Thread.stop     : ada, kini selalu error
SecurityManager : dinonaktifkan permanen

Bedah baris-per-baris untuk bagian pentingnya:

  1. Baris Runtime.version().feature(): mengembalikan nomor rilis fitur (25), komponen paling berguna untuk pemeriksaan programatik; build() berupa OptionalInt yang dicetak sebagai angka build JDK.
  2. Method methodAda(Class, String): mencari method publik lewat reflection — Thread.suspend tidak ditemukan (dihapus dari JDK modern), sementara Thread.stop masih tercantum tetapi selalu melempar UnsupportedOperationException saat dipanggil; javadoc JDK 25 menyebutnya secara eksplisit “UnsupportedOperationException - always”.
  3. Blok System.setSecurityManager(null): sejak Java 24 (JEP 486), Security Manager dinonaktifkan permanen; pemanggilannya melempar UnsupportedOperationException — aplikasi lawas yang memasang Security Manager harus dirombak pada bagian itu.

Menyasar Versi Lama dari JDK Baru #

Compiler modern dapat menghasilkan class file untuk JVM lama lewat flag --release, sekaligus memeriksa kode terhadap API yang benar dari versi target:

bash
javac --release 17 Aplikasi.java
Versi targetClass file (major)Catatan
Java 852Masih umum di bank dan instansi
Java 1155Titik lompatan umum dari 8
Java 1761Basis banyak framework modern
Java 2165LTS sebelumnya
Java 2569Target Course ini

Memindai API Usang Sebelum Upgrade #

Dua alat bawaan JDK memindai aplikasi tanpa menjalankannya. jdeprscan mencari pemakaian API deprecated — khususnya yang ditandai for removal:

bash
jdeprscan --release 25 --for-removal aplikasi.jar
jdeps --jdk-internals aplikasi.jar

jdeps --jdk-internals melengkapi dengan menemukan pemakaian internal JDK (sun.misc.*, dan kawan-kawan) yang tidak dijamin stabil antar versi — kategori yang paling sering mematahkan aplikasi lama. Hasil keduanya menjadi daftar kerja pra-upgrade yang konkret: per-API, per-class.

Setelah seluruh fitur modern terpetakan dan jalur upgrade jelas, Section ini tertutup. Aplikasi Java kini dapat ditulis dengan record, switch expression, virtual thread, dan pola-pola modern lainnya — lalu dipelihara lintas versi dengan rencana, bukan dengan harapan.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

CekMigrasi.java

Unduh berkas
java
public class CekMigrasi {

    // Deteksi API lama lewat reflection: ada, dihapus, atau dinonaktifkan?
    static boolean methodAda(Class<?> kelas, String nama) {
        try {
            kelas.getMethod(nama);
            return true;
        } catch (NoSuchMethodException e) {
            return false;
        }
    }

    public static void main(String[] args) {
        var versi = Runtime.version();
        System.out.println("runtime  : Java " + versi.feature()
                + " build " + versi.build().orElse(-1));

        int[] tonggak = {8, 11, 17, 21, 25};
        for (int v : tonggak) {
            String status = versi.feature() >= v ? "sudah terlewati" : "belum tercapai";
            System.out.printf("tonggak %2d : %s%n", v, status);
        }

        System.out.println("Thread.suspend  : "
                + (methodAda(Thread.class, "suspend") ? "ada" : "sudah dihapus"));
        System.out.println("Thread.stop     : "
                + (methodAda(Thread.class, "stop") ? "ada, kini selalu error" : "sudah dihapus"));

        // SecurityManager dinonaktifkan permanen sejak Java 24 (JEP 486).
        try {
            System.setSecurityManager(null);
        } catch (UnsupportedOperationException e) {
            System.out.println("SecurityManager : dinonaktifkan permanen");
        }
    }
}