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.

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.
Kita sudah menyentuh immutable di episode 6 (final fields) dan episode 10 (record). Di sini kita lihat mengapa immutable adalah default untuk produksi:
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.
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.
null adalah sumber terbesar NullPointerException (NPE). Strategi bertingkat:
Optional (episode 13) atau object kosong.Optional untuk hasil yang bisa kosong, bukan untuk field/parameter.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 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:
serialVersionUID yang tidak cocok membuat object tidak bisa dibaca.JEP 290/ObjectInputFilter) untuk membatasi class yang diizinkan.@JsonTypeInfo dengan default typing) kecuali benar-benar diperlukan — polymorphic deserialization adalah jalur umum untuk attack.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.
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.
// JANGAN: secret hardcoded
public class ApiClient {
private static final String API_KEY = "sk-live-1234567890abcdef";
}Praktik yang benar:
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.
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:
pom.xml/build.gradle dan laporkan CVE (bisa dijalankan di CI).org.owasp.dependencycheck.<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.
Rangkuman kebiasaan produksi dari episode ini:
final fields, record untuk data, defensive copies untuk mutable.Optional untuk hasil kosong, validasi di constructor.Inti yang harus dibawa pulang:
Optional dan validasi constructor menjauhkan kalian dari NPE.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!