Wednesday

18-06-2025 Vol 19

Beginner’s Guide to Exception Handling in Spring Boot

Panduan Pemula untuk Penanganan Pengecualian di Spring Boot

Pengecualian tak terhindarkan dalam pengembangan aplikasi, terutama yang kompleks seperti aplikasi Spring Boot. Menangani pengecualian dengan benar sangat penting untuk memastikan aplikasi yang stabil, tangguh, dan mudah dipelihara. Panduan ini ditujukan untuk pemula dan akan memandu Anda melalui dasar-dasar penanganan pengecualian di Spring Boot, praktik terbaik, dan contoh kode praktis.

Daftar Isi

  1. Apa itu Pengecualian?
  2. Mengapa Penanganan Pengecualian Penting?
  3. Jenis Pengecualian di Java
  4. Penanganan Pengecualian di Java Dasar
    1. Blok try-catch
    2. Blok finally
    3. Kata Kunci throw
    4. Kata Kunci throws
  5. Penanganan Pengecualian di Spring Boot
    1. @ExceptionHandler
    2. @ControllerAdvice
    3. ResponseEntityExceptionHandler
    4. Kelas Pengecualian Kustom
    5. Validasi dan Pengecualian
  6. Praktik Terbaik Penanganan Pengecualian
    1. Tangkap Pengecualian Spesifik
    2. Catat Pengecualian
    3. Kembalikan Respons Kesalahan yang Bermakna
    4. Hindari Menelan Pengecualian
    5. Gunakan Pengecualian Kustom
  7. Contoh Kode Penuh
    1. Struktur Proyek
    2. Dependencies
    3. Model
    4. Controller
    5. Service
    6. Contoh Pengecualian Kustom
  8. Kesimpulan
  9. Referensi

Apa itu Pengecualian?

Pengecualian adalah peristiwa abnormal yang terjadi selama eksekusi program dan mengganggu aliran instruksi yang normal. Dalam istilah sederhana, itu adalah kesalahan yang terjadi saat program berjalan. Contoh umum termasuk mencoba mengakses elemen di luar batas array, membagi dengan nol, atau tidak dapat menemukan file.

Di Java, pengecualian adalah objek yang merupakan turunan dari kelas Throwable. Kelas Throwable memiliki dua subkelas utama: Exception dan Error.

  • Exception: Mewakili kondisi yang masuk akal bagi aplikasi untuk ditangkap dan dipulihkan. Ini biasanya disebabkan oleh kesalahan dalam kode aplikasi, kondisi eksternal yang tidak terduga, atau kesalahan input.
  • Error: Mewakili masalah serius yang tidak boleh dicoba ditangani oleh aplikasi. Ini biasanya menunjukkan kesalahan JVM, kehabisan memori, atau masalah sistem lainnya.

Mengapa Penanganan Pengecualian Penting?

Penanganan pengecualian yang baik sangat penting untuk beberapa alasan:

  • Stabilitas Aplikasi: Mencegah aplikasi mogok saat terjadi kesalahan. Dengan menangani pengecualian dengan benar, Anda dapat menjaga aplikasi tetap berjalan, meskipun terjadi masalah yang tidak terduga.
  • Pengalaman Pengguna: Memberikan pesan kesalahan yang bermakna kepada pengguna alih-alih menampilkan tumpukan jejak yang tidak ramah. Ini membantu pengguna memahami apa yang salah dan bagaimana memperbaikinya.
  • Kemudahan Pemeliharaan: Mempermudah debug dan pemeliharaan kode. Penanganan pengecualian yang tepat menyederhanakan identifikasi dan memperbaiki kesalahan.
  • Keamanan: Mencegah informasi sensitif bocor melalui pesan kesalahan. Dengan mengendalikan informasi yang dikembalikan dalam respons kesalahan, Anda dapat mencegah penyerang memperoleh informasi tentang sistem Anda.
  • Ketahanan: Meningkatkan ketahanan aplikasi. Aplikasi yang dapat menangani pengecualian dengan baik lebih mampu bertahan dalam menghadapi masalah yang tidak terduga.

Jenis Pengecualian di Java

Java membagi pengecualian menjadi dua kategori utama:

  • Checked Exceptions: Pengecualian yang harus ditangani oleh kode atau dideklarasikan menggunakan kata kunci throws. Ini diverifikasi pada waktu kompilasi, memaksa pengembang untuk memperhatikan potensi masalah. Contoh: IOException, SQLException.
  • Unchecked Exceptions (Runtime Exceptions): Pengecualian yang tidak perlu ditangani oleh kode atau dideklarasikan. Ini terjadi selama waktu proses dan biasanya disebabkan oleh kesalahan pemrograman. Contoh: NullPointerException, IllegalArgumentException, IndexOutOfBoundsException.

Penanganan Pengecualian di Java Dasar

Sebelum membahas penanganan pengecualian Spring Boot, mari kita tinjau dasar-dasar penanganan pengecualian Java.

Blok try-catch

Blok try-catch digunakan untuk menangani pengecualian. Kode yang mungkin menghasilkan pengecualian diletakkan di dalam blok try, dan kode untuk menangani pengecualian ditempatkan di dalam blok catch.


try {
  // Kode yang mungkin menghasilkan pengecualian
  int result = 10 / 0; // ArithmeticException
} catch (ArithmeticException e) {
  // Kode untuk menangani pengecualian
  System.err.println("Terjadi kesalahan aritmatika: " + e.getMessage());
}
  

Blok finally

Blok finally bersifat opsional dan dieksekusi setelah blok try dan blok catch, terlepas dari apakah pengecualian terjadi atau tidak. Ini biasanya digunakan untuk melepaskan sumber daya, seperti menutup file atau koneksi database.


try {
  // Kode yang mungkin menghasilkan pengecualian
  FileInputStream fis = new FileInputStream("file.txt");
  // ... lakukan sesuatu dengan file ...
} catch (IOException e) {
  // Tangani pengecualian
  System.err.println("Terjadi kesalahan I/O: " + e.getMessage());
} finally {
  // Pastikan file ditutup
  try {
    if (fis != null) {
      fis.close();
    }
  } catch (IOException e) {
    System.err.println("Gagal menutup file: " + e.getMessage());
  }
}
  

Kata Kunci throw

Kata kunci throw digunakan untuk melempar pengecualian secara manual. Ini dapat berguna untuk memberi sinyal kesalahan dari metode atau untuk mengubah jenis pengecualian.


public void processData(String data) {
  if (data == null || data.isEmpty()) {
    throw new IllegalArgumentException("Data tidak boleh kosong atau null.");
  }
  // ... proses data ...
}
  

Kata Kunci throws

Kata kunci throws digunakan dalam deklarasi metode untuk menunjukkan bahwa metode mungkin melempar pengecualian tertentu. Ini memaksa pemanggil metode untuk menangani pengecualian atau mendeklarasikannya dengan kata kunci throws.


public void readFile(String filePath) throws IOException {
  FileInputStream fis = new FileInputStream(filePath);
  // ... baca dari file ...
  fis.close();
}
  

Penanganan Pengecualian di Spring Boot

Spring Boot menyediakan beberapa cara yang ampuh dan fleksibel untuk menangani pengecualian di aplikasi Anda. Mari kita jelajahi beberapa pendekatan yang paling umum digunakan.

@ExceptionHandler

Anotasi @ExceptionHandler memungkinkan Anda untuk menangani pengecualian tertentu di dalam controller. Ini sangat berguna untuk menangani pengecualian yang terkait dengan tindakan controller tertentu.


@Controller
public class UserController {

  @GetMapping("/users/{id}")
  public User getUser(@PathVariable Long id) {
    // Kode untuk mengambil pengguna dari database
    if (user == null) {
      throw new UserNotFoundException("Pengguna dengan ID " + id + " tidak ditemukan.");
    }
    return user;
  }

  @ExceptionHandler(UserNotFoundException.class)
  public ResponseEntity<ErrorResponse> handleUserNotFoundException(UserNotFoundException ex) {
    ErrorResponse errorResponse = new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage());
    return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
  }
}
  

Dalam contoh ini, handleUserNotFoundException akan dipanggil jika UserNotFoundException dilempar di dalam UserController. Metode ini membuat objek ErrorResponse dan mengembalikan respons dengan status HTTP 404 (NOT_FOUND).

@ControllerAdvice

Anotasi @ControllerAdvice memungkinkan Anda untuk membuat handler pengecualian global yang berlaku untuk semua controller di aplikasi Anda. Ini sangat berguna untuk menangani pengecualian yang terjadi di seluruh aplikasi.


@ControllerAdvice
public class GlobalExceptionHandler {

  @ExceptionHandler(Exception.class)
  public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex) {
    ErrorResponse errorResponse = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Terjadi kesalahan internal.");
    // Log pengecualian
    return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
  }

  @ExceptionHandler(MethodArgumentNotValidException.class)
  public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex) {
    List<String> errors = ex.getBindingResult()
        .getFieldErrors()
        .stream()
        .map(DefaultMessageSourceResolvable::getDefaultMessage)
        .collect(Collectors.toList());

    ErrorResponse errorResponse = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), "Validasi gagal", errors);
    return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
  }
}
  

Dalam contoh ini, handleGlobalException akan dipanggil jika pengecualian dilempar di controller mana pun yang tidak memiliki handler khusus. Metode ini membuat objek ErrorResponse dan mengembalikan respons dengan status HTTP 500 (INTERNAL_SERVER_ERROR). handleValidationException menangani pengecualian validasi dan mengembalikan respons dengan pesan kesalahan validasi terperinci.

ResponseEntityExceptionHandler

Kelas ResponseEntityExceptionHandler adalah kelas dasar yang nyaman untuk menangani pengecualian yang dilempar oleh Spring MVC. Ini menyediakan penanganan default untuk berbagai pengecualian MVC, seperti HttpRequestMethodNotSupportedException dan MissingServletRequestParameterException. Anda dapat memperluas kelas ini untuk menyesuaikan penanganan pengecualian default atau menambahkan handler untuk pengecualian kustom Anda.


@ControllerAdvice
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

  @Override
  protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(
      HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
    ErrorResponse errorResponse = new ErrorResponse(HttpStatus.METHOD_NOT_ALLOWED.value(), ex.getMessage());
    return new ResponseEntity<>(errorResponse, HttpStatus.METHOD_NOT_ALLOWED);
  }

  @ExceptionHandler(UserNotFoundException.class)
  public ResponseEntity<Object> handleUserNotFoundException(UserNotFoundException ex, WebRequest request) {
    ErrorResponse errorResponse = new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage());
    return handleExceptionInternal(ex, errorResponse, new HttpHeaders(), HttpStatus.NOT_FOUND, request);
  }
}
  

Dalam contoh ini, kita memperluas ResponseEntityExceptionHandler dan menimpa metode handleHttpRequestMethodNotSupported untuk memberikan respons kesalahan kustom untuk pengecualian HttpRequestMethodNotSupportedException. Kita juga menambahkan handler untuk UserNotFoundException menggunakan anotasi @ExceptionHandler.

Kelas Pengecualian Kustom

Membuat kelas pengecualian kustom memungkinkan Anda untuk membuat pengecualian yang lebih bermakna dan khusus untuk domain aplikasi Anda. Ini meningkatkan kejelasan kode dan mempermudah penanganan kesalahan secara efektif.


public class UserNotFoundException extends RuntimeException {

  public UserNotFoundException(String message) {
    super(message);
  }
}

public class InsufficientFundsException extends Exception {

  public InsufficientFundsException(String message) {
    super(message);
  }
}
  

Dalam contoh ini, kita mendefinisikan dua kelas pengecualian kustom: UserNotFoundException (runtime exception) dan InsufficientFundsException (checked exception). Anda dapat menambahkan bidang atau metode tambahan ke kelas pengecualian kustom Anda untuk memberikan informasi kesalahan yang lebih rinci.

Validasi dan Pengecualian

Spring Boot menyediakan mekanisme validasi yang kuat yang dapat digunakan untuk memvalidasi input pengguna dan mencegah kesalahan. Anda dapat menggunakan anotasi seperti @NotNull, @NotEmpty, @Size, dan @Email untuk mendeklarasikan batasan validasi pada bidang model Anda. Jika validasi gagal, MethodArgumentNotValidException dilempar, yang dapat Anda tangani menggunakan @ControllerAdvice, seperti yang ditunjukkan dalam contoh sebelumnya.


public class User {

  @NotNull(message = "Nama tidak boleh null")
  @NotEmpty(message = "Nama tidak boleh kosong")
  private String name;

  @Email(message = "Format email tidak valid")
  private String email;

  // ... getter dan setter ...
}

@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
  // ... proses pengguna ...
  return new ResponseEntity<>(user, HttpStatus.CREATED);
}
  

Dalam contoh ini, anotasi @Valid menyebabkan Spring Boot memvalidasi objek User sebelum diproses oleh metode createUser. Jika validasi gagal, MethodArgumentNotValidException akan dilempar.

Praktik Terbaik Penanganan Pengecualian

Berikut adalah beberapa praktik terbaik untuk penanganan pengecualian di Spring Boot:

Tangkap Pengecualian Spesifik

Selalu tangkap pengecualian sespesifik mungkin. Ini memungkinkan Anda untuk menangani berbagai jenis kesalahan secara berbeda dan memberikan respons yang lebih tepat.


try {
  // Kode yang mungkin menghasilkan pengecualian
} catch (ArithmeticException e) {
  // Tangani kesalahan aritmatika
} catch (NullPointerException e) {
  // Tangani pengecualian null pointer
} catch (Exception e) {
  // Tangani semua pengecualian lainnya
}
  

Hindari menangkap pengecualian generik (seperti Exception atau Throwable) kecuali benar-benar diperlukan. Menangkap pengecualian generik dapat menyembunyikan kesalahan dan mempersulit debug.

Catat Pengecualian

Selalu catat pengecualian, bersama dengan konteksnya. Ini membantu Anda untuk mendiagnosis dan memperbaiki kesalahan dengan cepat. Gunakan framework logging seperti Logback atau Log4j untuk mencatat pengecualian ke file atau database.


private static final Logger logger = LoggerFactory.getLogger(UserController.class);

try {
  // Kode yang mungkin menghasilkan pengecualian
} catch (Exception e) {
  logger.error("Terjadi kesalahan saat memproses pengguna:", e);
  // ... tangani pengecualian ...
}
  

Pastikan untuk menyertakan informasi yang relevan dalam log, seperti pesan kesalahan, tumpukan jejak, dan input pengguna.

Kembalikan Respons Kesalahan yang Bermakna

Kembalikan respons kesalahan yang bermakna kepada pengguna. Hindari menampilkan tumpukan jejak atau pesan kesalahan teknis yang tidak membantu. Sebagai gantinya, kembalikan pesan yang jelas dan ringkas yang menjelaskan apa yang salah dan bagaimana memperbaikinya.


{
  "status": 404,
  "message": "Pengguna dengan ID 123 tidak ditemukan."
}
  

Gunakan kode status HTTP yang sesuai untuk menunjukkan jenis kesalahan yang terjadi. Misalnya, gunakan kode status 400 (BAD_REQUEST) untuk kesalahan validasi, kode status 404 (NOT_FOUND) untuk sumber daya yang tidak ditemukan, dan kode status 500 (INTERNAL_SERVER_ERROR) untuk kesalahan internal.

Hindari Menelan Pengecualian

Menelan pengecualian berarti menangkap pengecualian dan kemudian melakukan apa-apa dengannya (atau hanya mencatatnya tanpa melakukan tindakan lebih lanjut). Ini adalah praktik yang buruk karena dapat menyembunyikan kesalahan dan mempersulit debug.


try {
  // Kode yang mungkin menghasilkan pengecualian
} catch (Exception e) {
  // Jangan lakukan ini!
  logger.error("Terjadi kesalahan:", e);
}
  

Jika Anda menangkap pengecualian, Anda harus melakukan sesuatu dengannya: tangani dengan benar, lempar ulang, atau transformasikan menjadi pengecualian lain.

Gunakan Pengecualian Kustom

Gunakan pengecualian kustom untuk mewakili kesalahan yang khusus untuk domain aplikasi Anda. Ini meningkatkan kejelasan kode dan mempermudah penanganan kesalahan secara efektif.

Saat membuat pengecualian kustom, pastikan untuk memberikan nama yang bermakna dan menyertakan informasi yang relevan dalam pesan kesalahan.

Contoh Kode Penuh

Berikut adalah contoh kode penuh yang menunjukkan penanganan pengecualian di Spring Boot:

Struktur Proyek


.
├── pom.xml
└── src
    └── main
        └── java
            └── com
                └── example
                    └── exceptionhandling
                        ├── controller
                        │   └── UserController.java
                        ├── exception
                        │   ├── ErrorResponse.java
                        │   ├── GlobalExceptionHandler.java
                        │   └── UserNotFoundException.java
                        ├── model
                        │   └── User.java
                        └── service
                            └── UserService.java
  

Dependencies

Berikut adalah dependencies yang dibutuhkan dalam `pom.xml`:


<dependencies>
  <!-- Spring Boot Starter Web -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <!-- Spring Boot Starter Validation -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
  </dependency>

  <!-- Lombok -->
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
  </dependency>

  <!-- Spring Boot Starter Test -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>
  

Model

`User.java`:


import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

@Data
public class User {

  private Long id;

  @NotNull(message = "Nama tidak boleh null")
  @NotEmpty(message = "Nama tidak boleh kosong")
  private String name;

  @Email(message = "Format email tidak valid")
  private String email;
}
  

Controller

`UserController.java`:


import com.example.exceptionhandling.exception.UserNotFoundException;
import com.example.exceptionhandling.model.User;
import com.example.exceptionhandling.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;

@RestController
@RequestMapping("/users")
public class UserController {

  @Autowired
  private UserService userService;

  @GetMapping
  public ResponseEntity<List<User>> getAllUsers() {
    return new ResponseEntity<>(userService.getAllUsers(), HttpStatus.OK);
  }

  @GetMapping("/{id}")
  public ResponseEntity<User> getUser(@PathVariable Long id) {
    User user = userService.getUserById(id);
    if (user == null) {
      throw new UserNotFoundException("Pengguna dengan ID " + id + " tidak ditemukan.");
    }
    return new ResponseEntity<>(user, HttpStatus.OK);
  }

  @PostMapping
  public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
    return new ResponseEntity<>(userService.createUser(user), HttpStatus.CREATED);
  }
}
  

Service

`UserService.java`:


import com.example.exceptionhandling.model.User;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class UserService {

  private List<User> users = new ArrayList<>();
  private Long nextId = 1L;

  public List<User> getAllUsers() {
    return users;
  }

  public User getUserById(Long id) {
    return users.stream()
        .filter(user -> user.getId().equals(id))
        .findFirst()
        .orElse(null);
  }

  public User createUser(User user) {
    user.setId(nextId++);
    users.add(user);
    return user;
  }
}
  

Contoh Pengecualian Kustom

`UserNotFoundException.java`:


import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {

  public UserNotFoundException(String message) {
    super(message);
  }
}
  

`ErrorResponse.java`:


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ErrorResponse {

  private int status;
  private String message;
  private List<String> errors;

  public ErrorResponse(int status, String message) {
    this.status = status;
    this.message = message;
  }
}
  

`GlobalExceptionHandler.java`:


import com.example.exceptionhandling.exception.ErrorResponse;
import com.example.exceptionhandling.exception.UserNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;

@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

  @Override
  protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
    List<String> errors = ex.getBindingResult()
        .getFieldErrors()
        .stream()
        .map(DefaultMessageSourceResolvable::getDefaultMessage)
        .collect(Collectors.toList());

    ErrorResponse errorResponse = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), "Validasi gagal", errors);
    return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
  }

  @ExceptionHandler(UserNotFoundException.class)
  public ResponseEntity<ErrorResponse> handleUserNotFoundException(UserNotFoundException ex) {
    ErrorResponse errorResponse = new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage());
    return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
  }

  @ExceptionHandler(Exception.class)
  public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex) {
    ErrorResponse errorResponse = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Terjadi kesalahan internal.");
    return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
  }
}
  

Kesimpulan

Penanganan pengecualian adalah bagian penting dari pengembangan aplikasi yang kuat dan andal. Dengan memahami dasar-dasar penanganan pengecualian di Java dan memanfaatkan fitur penanganan pengecualian Spring Boot, Anda dapat membuat aplikasi yang lebih stabil, mudah dipelihara, dan mudah digunakan. Ingatlah untuk selalu menangkap pengecualian spesifik, mencatat kesalahan, dan mengembalikan respons kesalahan yang bermakna kepada pengguna. Dengan mengikuti praktik terbaik ini, Anda dapat meningkatkan kualitas dan ketahanan aplikasi Spring Boot Anda.

Referensi

“`

omcoding

Leave a Reply

Your email address will not be published. Required fields are marked *