Belajar OOP dengan Java - Keamanan & Best Practice
Series/Belajar OOP/Episode 18
Episode 18 of 23

Belajar OOP dengan Java - Keamanan & Best Practice

Menerapkan best practice produksi: immutability dengan defensive copies dan final fields, menghindari null dengan Optional, lalu risiko serialization & deserialization, secret handling yang aman, dan dependency scanning sebagai bagian dari keamanan aplikasi Java.

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

Sampai episode 17, kalian belajar membangun fitur. Episode ini mengubah lensa: bagaimana membuat kode kuat — tidak hanya berfungsi, tetapi tahan terhadap penyalahgunaan, tahan terhadap kegagalan tak terduga, dan aman di produksi. Inilah fase production hardening.

Mengapa topik ini sering diabaikan sampai terlambat? Karena keamanan dan best practice terasa abstrak sampai ada insiden: secret bocor ke log, data rusak karena mutable state, atau classpath yang disuntik kode jahat lewat deserialization. Episode ini memberi kebiasaan yang mencegah kelas masalah itu sejak awal.

Immutability

Kekuatan Object Tak Berubah

Kita sudah menyentuh immutable di episode 6 (final fields) dan episode 10 (record). Di sini kita lihat mengapa immutable adalah default untuk produksi:

  • Thread-safe — tidak ada race condition (episode 17).
  • Dapat diprediksi — tidak ada perubahan tak terduga saat object dibagikan.
  • Cache aman — object immutable bisa dipakai sebagai key Map tanpa takut berubah.
JavaObject immutable lengkap
public final class Pesanan {
    private final String id;
    private final List<String> item;
 
    public Pesanan(String id, List<String> item) {
        this.id = id;
        this.item = new ArrayList<>(item); // defensive copy
    }
 
    public List<String> getItem() {
        return new ArrayList<>(item);      // defensive copy
    }
}

Perhatikan dua defensive copy: di constructor (jangan simpan reference pemanggil) dan di getter (jangan ekspos reference internal). Tanpa ini, Pesanan yang "immutable" bisa diubah dari luar.

Defensive Copies: Aturan Tangan Kedua

Saat menerima atau mengembalikan object mutable (array, List, Date), jangan percaya reference yang lewat. Salin sebelum disimpan, salin sebelum dikembalikan. Alternatif yang lebih bersih: kembalikan view tak-ubah dengan Collections.unmodifiableList(...) — pemanggil tidak bisa mengubahnya, tanpa biaya salin penuh.

Menghindari Null

Null: The Billion Dollar Mistake

null adalah sumber terbesar NullPointerException (NPE). Strategi bertingkat:

  1. Jangan kembalikan null — jika bisa kosong, kembalikan Optional (episode 13) atau object kosong.
  2. Validasi input di constructor — jangan biarkan null masuk (sudah kita lakukan di episode 5).
  3. Gunakan Optional untuk hasil yang bisa kosong, bukan untuk field/parameter.
JavaMenghindari null
import java.util.Optional;
 
public class Direktori {
    public Optional<String> cariTelepon(String nama) {
        if (!daftar.containsKey(nama)) {
            return Optional.empty();
        }
        return Optional.of(daftar.get(nama));
    }
}

Pemanggil dipaksa berpikir tentang kemungkinan kosong. Java 8+ juga menyediakan bantuan: Objects.requireNonNull(...) melempar NPE dengan pesan jelas, dan String.isBlank()/isEmpty() memvalidasi teks.

Tip

Buat aturan kontrak di constructor: setiap parameter wajib diverifikasi (non-null, nilai valid). Object yang lahir valid lebih baik daripada object yang lahir cacat lalu diperbaiki di sana-sini — prinsip ini kembali ke invarian di episode 6.

Serialization & Deserialization

Risiko

Serialization mengubah object menjadi byte (untuk disimpan/dikirim); deserialization mengembalikannya. Java serialization klasik (implements Serializable) terkenal berbahaya: proses deserialization bisa menjalankan kode dari data yang dimanipulasi penyerang — inilah akar banyak CVE (termasuk serangan gadget chain).

Risiko utamanya:

  • Unsafe deserialization — data jahat mengeksekusi kode (RCE).
  • Data manipulasi — object hasil deserialization tidak melewati constructor, jadi validasi/invarian bisa dilewati.
  • Kompatibilitas rapuhserialVersionUID yang tidak cocok membuat object tidak bisa dibaca.

Praktik Aman

  1. Jangan gunakan Java native serialization untuk data yang bisa datang dari luar. Prefer format terstruktur: JSON (Jackson/Gson) atau Protobuf. JSON tidak bisa memicu gadget chain — datanya murni data.
  2. Jika terpaksa memakai serialization native, jangan deserialisasi input dari sumber tidak tepercaya, dan aktifkan filter class (JEP 290/ObjectInputFilter) untuk membatasi class yang diizinkan.
  3. Saat memakai JSON: konfigurasi deserialization untuk menolak polymorphic type hints dari input (@JsonTypeInfo dengan default typing) kecuali benar-benar diperlukan — polymorphic deserialization adalah jalur umum untuk attack.
JavaDeserialisasi JSON yang aman
import com.fasterxml.jackson.databind.ObjectMapper;
 
public class Pengurai {
    private final ObjectMapper mapper = new ObjectMapper();
 
    public DataTerima parse(String json) throws Exception {
        // Input dianggap data biasa, bukan definisi tipe
        return mapper.readValue(json, DataTerima.class);
    }
}

Tetap ke tipe konkret (DataTerima.class) dan hindari fitur default typing yang memungkinkan input menentukan class — itu titik masuk utama eksploitasi.

Secret Handling

Jangan Secret di Kode

Aturan emas: secret tidak pernah masuk ke source code. Bukan hanya password literal, tetapi juga default key, token di test, dan konfigurasi yang di-commit ke git.

JavaAnti-pattern: secret di kode
// JANGAN: secret hardcoded
public class ApiClient {
    private static final String API_KEY = "sk-live-1234567890abcdef";
}

Praktik yang benar:

  • Baca dari environment variable atau secret manager (AWS Secrets Manager, Vault, dsb.) di runtime.
  • Set di deployment config, bukan di source.
  • Jangan pernah log secret; gunakan masking.
JavaSecret dari environment
public class ApiClient {
    private static final String API_KEY = System.getenv("API_KEY");
 
    public ApiClient() {
        if (API_KEY == null || API_KEY.isBlank()) {
            throw new IllegalStateException("API_KEY belum diset di environment");
        }
    }
}

Selain itu, awasi git history: secret yang pernah ter-commit dianggap bocor meski sudah dihapus — rotasi (ganti key) adalah satu-satunya perbaikan.

Dependency Scanning

Supply Chain: Titik Lemah Modern

Sebagian besar aplikasi Java bergantung pada ratusan library (Maven/Gradle). Setiap dependency adalah permukaan serangan — versi yang sudah diketahui rentan (CVE) sering menjadi jalan masuk termudah.

Praktiknya:

  • OWASP Dependency-Check — scan pom.xml/build.gradle dan laporkan CVE (bisa dijalankan di CI).
  • Dependabot / Renovate — PR otomatis untuk dependency yang usang.
  • Gradle/Maven plugin audit — mis. plugin org.owasp.dependencycheck.
  • Jangan pin versi lama "selama masih jalan" — versi lama tanpa patch adalah kewajiban; jadwalkan upgrade.
OWASP Dependency-Check di Maven
<plugin>
    <groupId>org.owasp</groupId>
    <artifactId>dependency-check-maven</artifactId>
    <version>12.1.0</version>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Jalankan mvn verify → scan berjalan; build bisa dibuat gagal jika ditemukan CVE kritis — inilah cara otomasi menegakkan keamanan tanpa mengandalkan ingatan.

Warning

Dependency yang "tidak terpakai" pun berbahaya — mereka tetap ikut di classpath dan tetap ikut discan. Buang dependency mati, perbarui secara berkala, dan jangan menambah library hanya untuk satu fungsi kecil yang bisa ditulis sendiri.

Checklist Best Practice

Rangkuman kebiasaan produksi dari episode ini:

  • Immutable by defaultfinal fields, record untuk data, defensive copies untuk mutable.
  • Null hanya di perbatasanOptional untuk hasil kosong, validasi di constructor.
  • Jangan native serialization untuk input luar — JSON/Protobuf, tolak polymorphic type hints.
  • Secret dari environment — tidak pernah di source, tidak pernah di log, rotasi saat bocor.
  • Scan dependency di CI — OWASP Dependency-Check + update berkala.

Penutup

Inti yang harus dibawa pulang:

  • Immutable + defensive copies menghilangkan kelas bug mutasi tak terduga.
  • Optional dan validasi constructor menjauhkan kalian dari NPE.
  • Deserialization bisa menjadi RCE — hindari native serialization untuk input luar.
  • Secret hidup di environment/secret manager, bukan source code.
  • Dependency harus dipindai dan diperbarui — supply chain adalah permukaan serangan.

Di episode 19 selanjutnya kita akan membahas Java 25 LTS & fitur modern — structured concurrency, string templates, value objects preview, flexible constructor bodies, dan rekap fitur JDK 21. Sampai jumpa di episode 19!

Belajar OOP dengan Java - Keamanan & Best Practice | Belajar OOP