Praktik nyata pertama: membangun service SOAP di Java dengan JAX-WS memakai anotasi WebService dan WebMethod, publishing endpoint, menguji dengan curl dan SoapUI, hingga membangkitkan client dari WSDL dengan wsimport dan wsdl2java.

Setelah di episode 3 kita memahami kontrak WSDL, sekarang saatnya praktik nyata: membangun service SOAP pertama di Java. Kita memakai JAX-WS (Jakarta XML Web Services) — API standar Java untuk web services — yang sudah menjadi tulang punggung ekosistem SOAP Java selama hampir dua dekade.
Mengapa Java dulu? Karena ekosistem Java (JAX-WS, Metro, Apache CXF, WSS4J) adalah referensi industri untuk SOAP. Di episode berikutnya kita lihat bahasa lain; setelah episode ini, konsep yang sama akan terasa familier di PHP, .NET, dan Python.
Kita pakai Maven untuk mengelola dependensi. Service ini berjalan dengan publish endpoint bawaan JAX-WS, jadi tanpa server aplikasi eksternal:
mkdir -p soap-lab/bank-service && cd soap-lab/bank-service
mvn archetype:generate -DgroupId=id.devnull.soap \
-DartifactId=bank-service -DarchetypeArtifactId=maven-archetype-quickstartTambahkan dependensi Jakarta XML Web Services ke pom.xml:
<dependencies>
<dependency>
<groupId>jakarta.xml.ws</groupId>
<artifactId>jakarta.xml.ws-api</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-ri</artifactId>
<version>4.0.2</version>
</dependency>
</dependencies>Versi 4.0.x adalah rilis Jakarta EE yang kompatibel dengan JDK 17+ — versi inilah yang dipakai pada contoh di series ini.
Anotasi @WebService menandai class sebagai service SOAP; @WebMethod menandai method yang diekspos. Setiap method publik otomatis menjadi operasi WSDL:
package id.devnull.soap;
import jakarta.jws.WebMethod;
import jakarta.jws.WebParam;
import jakarta.jws.WebService;
import java.math.BigDecimal;
@WebService(name = "BankService", targetNamespace = "urn:example:bank")
public class BankService {
@WebMethod
public BigDecimal getBalance(
@WebParam(name = "accountNo") String accountNo) {
if ("ACC-001".equals(accountNo)) {
return new BigDecimal("2750000.00");
}
throw new IllegalArgumentException("Akun tidak ditemukan: " + accountNo);
}
}Perhatikan targetNamespace — ini menjadi namespace WSDL dan semua elemen pesan, mengikuti kontrak yang kita tulis di episode 3. Gaya argument document-literal dihasilkan otomatis oleh JAX-WS.
Note
throw IllegalArgumentException akan dipetakan JAX-WS menjadi SOAP Fault. Di episode 8 kita akan menangani pemetaan error bisnis secara eksplisit dengan @WebFault.
Untuk percobaan cepat, publish endpoint langsung dari main — tanpa Tomcat. JAX-WS menyediakan endpoint publisher bawaan:
package id.devnull.soap;
import jakarta.xml.ws.Endpoint;
public class Main {
public static void main(String[] args) {
String url = "http://localhost:8080/bank";
Endpoint.publish(url, new BankService());
System.out.println("Service berjalan di " + url + "?wsdl");
}
}Jalankan, lalu cek WSDL yang dibangkitkan JAX-WS dari kode (pola code-first):
mvn compile exec:java
curl -s http://localhost:8080/bank?wsdlJika WSDL muncul, service kalian sudah hidup. Ini contoh perfect untuk memahami pola code-first dari episode 3: kode adalah sumber kebenaran, WSDL di-generate.
Cara paling jujur menguji service adalah mengirim raw SOAP via curl. Perhatikan namespace SOAP 1.1 yang dipakai JAX-WS default:
curl -s -X POST http://localhost:8080/bank \
-H 'Content-Type: text/xml; charset=utf-8' \
-d '<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:bank="urn:example:bank">
<soap:Body>
<bank:getBalance>
<bank:accountNo>ACC-001</bank:accountNo>
</bank:getBalance>
</soap:Body>
</soap:Envelope>'Response datang sebagai envelope SOAP dengan elemen getBalanceResponse — persis seperti kontrak di episode 2.
Keunggulan SOAP: client bisa dibangkitkan langsung dari WSDL. Java menyediakan wsimport (bagian dari JDK, sekarang di bawah jaxws tools) dan CXF menyediakan wsdl2java:
wsimport -d ./target/generated -p id.devnull.soap.client \
http://localhost:8080/bank?wsdlKode client yang dibangkitkan dipakai seperti ini:
package id.devnull.soap;
import id.devnull.soap.client.BankService;
import id.devnull.soap.client.BankServicePortType;
import java.math.BigDecimal;
public class Client {
public static void main(String[] args) {
BankService service = new BankService();
BankServicePortType port = service.getBankPort();
BigDecimal balance = port.getBalance("ACC-001");
System.out.println("Saldo: " + balance);
}
}Tip
Jika project kalian memakai Apache CXF, gunakan wsdl2java dengan flag -wsdlLocation agar client tetap memakai WSDL jarak jauh saat runtime — berguna untuk testing dan staging. wsimport bawaan JDK lebih sederhana, sedangkan wsdl2java lebih fleksibel.
wsimport — tool ini tidak lagi dibundel di JDK baru; install sebagai plugin Maven (jaxws-maven-plugin) atau gunakan wsdl2java dari CXF.@WebMethod(exclude = true) — method publik yang tidak ingin diekspos harus diberi anotasi ini; jika tidak, JAX-WS mengeksposnya.soap:address location memakai hostname/port saat publish; saat deploy lintas host, sesuaikan location atau pakai Tomcat/CXF.Inti yang harus dibawa pulang:
@WebService/@WebMethod; WSDL di-generate otomatis (code-first).Endpoint.publish cukup untuk lab; produksi butuh server aplikasi (Tomcat) atau CXF.wsimport/wsdl2java membangkitkan client langsung dari WSDL — kekuatan kontrak machine-readable.Di episode 5 selanjutnya kita akan melihat SOAP di PHP, .NET, dan Python — SoapClient/SoapServer bawaan PHP, WCF & dotnet-svcutil di .NET, dan library zeep di Python — lengkap dengan contoh client dan service untuk tiap bahasa. Sampai jumpa di episode 5!