ভূমিকা
যেকোনো এন্টারপ্রাইজ অ্যাপ্লিকেশন তৈরিতে এক্সেপশন হ্যান্ডলিং একটি অত্যন্ত গুরুত্বপূর্ণ বিষয়। সঠিকভাবে এক্সেপশন হ্যান্ডেল না করলে অ্যাপ্লিকেশন ক্র্যাশ করতে পারে, ব্যবহারকারীকে অর্থহীন এরর মেসেজ দেখাতে পারে এবং সিকিউরিটি ঝুঁকি তৈরি হতে পারে। স্প্রিং বুট আমাদের এক্সেপশন হ্যান্ডলিং করার জন্য বেশ কিছু শক্তিশালী মেকানিজম প্রদান করে।
এই টিউটোরিয়ালে আমরা শিখবো:
- স্প্রিং বুটে এক্সেপশন হ্যান্ডলিং কেন দরকার
@ExceptionHandlerব্যবহার@ControllerAdviceএবং@RestControllerAdviceব্যবহার- কাস্টম এক্সেপশন তৈরি
- গ্লোবাল এক্সেপশন হ্যান্ডলার
- প্রপার এরর রেসপন্স স্ট্রাকচার
১. প্রজেক্ট সেটআপ
প্রথমে Spring Initializr থেকে একটি নতুন প্রজেক্ট তৈরি করুন নিচের ডিপেন্ডেন্সি সহ:
- Spring Web
- Spring Data JPA
- H2 Database (টেস্টিংয়ের জন্য)
- Lombok
pom.xml এ প্রয়োজনীয় ডিপেন্ডেন্সি:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
২. কাস্টম এক্সেপশন ক্লাস তৈরি
প্রফেশনাল অ্যাপ্লিকেশনে সবসময় নিজস্ব কাস্টম এক্সেপশন তৈরি করা উচিত। এতে কোড আরও পরিষ্কার এবং মেইনটেইনযোগ্য হয়।
ResourceNotFoundException
package com.example.demo.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
private String resourceName;
private String fieldName;
private Object fieldValue;
public ResourceNotFoundException(String resourceName, String fieldName, Object fieldValue) {
super(String.format("%s not found with %s : '%s'", resourceName, fieldName, fieldValue));
this.resourceName = resourceName;
this.fieldName = fieldName;
this.fieldValue = fieldValue;
}
public String getResourceName() { return resourceName; }
public String getFieldName() { return fieldName; }
public Object getFieldValue() { return fieldValue; }
}
BadRequestException
package com.example.demo.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {
public BadRequestException(String message) {
super(message);
}
public BadRequestException(String message, Throwable cause) {
super(message, cause);
}
}
৩. এরর রেসপন্স স্ট্রাকচার তৈরি
API থেকে একটি কনসিস্টেন্ট এরর রেসপন্স পাঠানো প্রফেশনাল ডেভেলপমেন্টের অংশ।
package com.example.demo.payload;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class ErrorResponse {
private int statusCode;
private String status;
private String message;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy HH:mm:ss")
private LocalDateTime timestamp;
private List<String> errors;
public ErrorResponse(int statusCode, String status, String message) {
this.statusCode = statusCode;
this.status = status;
this.message = message;
this.timestamp = LocalDateTime.now();
}
}
৪. @ExceptionHandler ব্যবহার (লোকাল হ্যান্ডলিং)
@ExceptionHandler অ্যানোটেশন দিয়ে একটি নির্দিষ্ট কন্ট্রোলারের মধ্যে এক্সেপশন হ্যান্ডেল করা যায়।
package com.example.demo.controller;
import com.example.demo.exception.ResourceNotFoundException;
import com.example.demo.payload.ErrorResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
public ResponseEntity<?> getUserById(@PathVariable Long id) {
// ধরুন এই user খুঁজে পাওয়া যায়নি
throw new ResourceNotFoundException("User", "id", id);
}
// এই কন্ট্রোলারের জন্য লোকাল এক্সেপশন হ্যান্ডলার
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFound(ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
HttpStatus.NOT_FOUND.getReasonPhrase(),
ex.getMessage()
);
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
}
সমস্যা: এই পদ্ধতিতে প্রতিটি কন্ট্রোলারে আলাদাভাবে হ্যান্ডলার লিখতে হয়, যা কোড ডুপ্লিকেশন তৈরি করে।
৫. @ControllerAdvice দিয়ে গ্লোবাল এক্সেপশন হ্যান্ডলিং
@ControllerAdvice বা @RestControllerAdvice ব্যবহার করে একটি সেন্ট্রালাইজড গ্লোবাল এক্সেপশন হ্যান্ডলার তৈরি করা যায়। এটি সব কন্ট্রোলারের এক্সেপশন একসাথে হ্যান্ডেল করে।
package com.example.demo.exception;
import com.example.demo.payload.ErrorResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import java.util.List;
import java.util.stream.Collectors;
@RestControllerAdvice
public class GlobalExceptionHandler {
// ResourceNotFoundException হ্যান্ডেল করা
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFoundException(
ResourceNotFoundException ex, WebRequest request) {
ErrorResponse error = new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
HttpStatus.NOT_FOUND.getReasonPhrase(),
ex.getMessage()
);
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
// BadRequestException হ্যান্ডেল করা
@ExceptionHandler(BadRequestException.class)
public ResponseEntity<ErrorResponse> handleBadRequestException(
BadRequestException ex, WebRequest request) {
ErrorResponse error = new ErrorResponse(
HttpStatus.BAD_REQUEST.value(),
HttpStatus.BAD_REQUEST.getReasonPhrase(),
ex.getMessage()
);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
// ভ্যালিডেশন এররর হ্যান্ডেল করা
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(
MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage())
.collect(Collectors.toList());
ErrorResponse error = new ErrorResponse(
HttpStatus.BAD_REQUEST.value(),
"Validation Failed",
"ইনপুট ভ্যালিডেশন ব্যর্থ হয়েছে"
);
error.setErrors(errors);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
// সকল অপ্রত্যাশিত এক্সেপশনের জন্য ফলব্যাক হ্যান্ডলার
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(
Exception ex, WebRequest request) {
ErrorResponse error = new ErrorResponse(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(),
"সার্ভারে অপ্রত্যাশিত একটি সমস্যা হয়েছে। অনুগ্রহ করে পরে আবার চেষ্টা করুন।"
);
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
৬. Bean Validation দিয়ে ইনপুট ভ্যালিডেশন
স্প্রিং বুটে javax.validation বা jakarta.validation ব্যবহার করে ইনপুট ভ্যালিডেট করা যায়।
DTO ক্লাসে ভ্যালিডেশন অ্যানোটেশন:
package com.example.demo.payload;
import jakarta.validation.constraints.*;
import lombok.Data;
@Data
public class CreateUserRequest {
@NotBlank(message = "নাম খালি রাখা যাবে না")
@Size(min = 2, max = 50, message = "নাম ২ থেকে ৫০ অক্ষরের মধ্যে হতে হবে")
private String name;
@NotBlank(message = "ইমেইল খালি রাখা যাবে না")
@Email(message = "সঠিক ইমেইল ফরম্যাট দিন")
private String email;
@NotNull(message = "বয়স দিতে হবে")
@Min(value = 18, message = "বয়স কমপক্ষে ১৮ হতে হবে")
@Max(value = 100, message = "বয়স ১০০ এর বেশি হতে পারবে না")
private Integer age;
}
কন্ট্রোলারে @Valid ব্যবহার:
@PostMapping
public ResponseEntity<?> createUser(@Valid @RequestBody CreateUserRequest request) {
// যদি ভ্যালিডেশন ফেইল করে, GlobalExceptionHandler স্বয়ংক্রিয়ভাবে
// MethodArgumentNotValidException হ্যান্ডেল করবে
return ResponseEntity.ok("ইউজার সফলভাবে তৈরি হয়েছে");
}
৭. HTTP স্ট্যাটাস কোড ও সঠিক ব্যবহার
| স্ট্যাটাস কোড | অর্থ | কখন ব্যবহার করবেন |
|---|---|---|
| 200 OK | সফল | সফল GET, PUT রিকোয়েস্টে |
| 201 Created | তৈরি হয়েছে | সফল POST এ নতুন রিসোর্স তৈরিতে |
| 400 Bad Request | খারাপ রিকোয়েস্ট | ইনভ্যালিড ইনপুট বা ভ্যালিডেশন ফেইলে |
| 401 Unauthorized | অনুমতি নেই | অথেন্টিকেশন ব্যর্থ হলে |
| 403 Forbidden | নিষিদ্ধ | অ্যাকসেস অনুমোদিত না হলে |
| 404 Not Found | পাওয়া যায়নি | রিসোর্স খুঁজে না পেলে |
| 409 Conflict | দ্বন্দ্ব | ডুপ্লিকেট ডেটা তৈরির চেষ্টায় |
| 500 Internal Server Error | সার্ভার এরর | অপ্রত্যাশিত সার্ভার সমস্যায় |
৮. সম্পূর্ণ উদাহরণ — User Service
package com.example.demo.service;
import com.example.demo.entity.User;
import com.example.demo.exception.BadRequestException;
import com.example.demo.exception.ResourceNotFoundException;
import com.example.demo.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User", "id", id));
}
public User createUser(User user) {
// ইমেইল আগে থেকে আছে কিনা চেক করা
if (userRepository.existsByEmail(user.getEmail())) {
throw new BadRequestException(
"এই ইমেইল দিয়ে আগেই একটি অ্যাকাউন্ট আছে: " + user.getEmail()
);
}
return userRepository.save(user);
}
public User updateUser(Long id, User userDetails) {
User user = getUserById(id); // এখানেও ResourceNotFoundException থ্রো হতে পারে
user.setName(userDetails.getName());
user.setEmail(userDetails.getEmail());
return userRepository.save(user);
}
}
৯. এরর রেসপন্সের উদাহরণ
সফল রেসপন্স (200 OK):
{
"id": 1,
"name": "রাহেলা বেগম",
"email": "rahela@example.com",
"age": 25
}
রিসোর্স না পাওয়া গেলে (404 Not Found):
{
"statusCode": 404,
"status": "Not Found",
"message": "User not found with id : '99'",
"timestamp": "18-06-2026 10:30:45",
"errors": null
}
ভ্যালিডেশন ফেইল (400 Bad Request):
{
"statusCode": 400,
"status": "Validation Failed",
"message": "ইনপুট ভ্যালিডেশন ব্যর্থ হয়েছে",
"timestamp": "18-06-2026 10:31:00",
"errors": [
"email: সঠিক ইমেইল ফরম্যাট দিন",
"age: বয়স কমপক্ষে ১৮ হতে হবে"
]
}
১০. সেরা অনুশীলন (Best Practices)
১. সবসময় কাস্টম এক্সেপশন ব্যবহার করুন: জেনেরিক RuntimeException এর বদলে নিজস্ব কাস্টম এক্সেপশন ব্যবহার করলে কোড আরও পরিষ্কার হয়।
২. কনসিস্টেন্ট এরর রেসপন্স: সব এরর রেসপন্সের একই স্ট্রাকচার থাকা উচিত। এতে ফ্রন্টএন্ড ডেভেলপারদের কাজ সহজ হয়।
৩. লগিং করুন: এক্সেপশন হ্যান্ডলারে log.error() দিয়ে এরর লগ করুন, কিন্তু ব্যবহারকারীকে স্ট্যাক ট্রেস দেখাবেন না।
৪. সেন্সিটিভ তথ্য লুকান: এরর মেসেজে ডেটাবেস ডিটেইল, স্ট্যাক ট্রেস বা ইন্টার্নাল সিস্টেম তথ্য কখনো পাঠাবেন না।
৫. HTTP স্ট্যাটাস কোড সঠিকভাবে ব্যবহার করুন: সব কিছুতে 500 না দিয়ে সঠিক স্ট্যাটাস কোড ব্যবহার করুন।
৬. @ResponseStatus অ্যানোটেশন: কাস্টম এক্সেপশন ক্লাসে @ResponseStatus যোগ করলে স্বয়ংক্রিয়ভাবে সঠিক HTTP স্ট্যাটাস পাঠানো হয়।
উপসংহার
স্প্রিং বুটে সঠিক এক্সেপশন হ্যান্ডলিং একটি রোবাস্ট এবং প্রফেশনাল API তৈরির মূল চাবিকাঠি। @RestControllerAdvice দিয়ে গ্লোবাল হ্যান্ডলার, কাস্টম এক্সেপশন ক্লাস এবং কনসিস্টেন্ট এরর রেসপন্স স্ট্রাকচার একসাথে ব্যবহার করলে আপনার অ্যাপ্লিকেশন আরও মেইনটেইনযোগ্য, ডিবাগযোগ্য এবং ব্যবহারকারীবান্ধব হবে।
এই টিউটোরিয়ালে আমরা কভার করেছি কাস্টম এক্সেপশন তৈরি, গ্লোবাল এক্সেপশন হ্যান্ডলার, ইনপুট ভ্যালিডেশন এবং সঠিক এরর রেসপন্স স্ট্রাকচার। এই কনসেপ্টগুলো আপনার প্রতিটি স্প্রিং বুট প্রজেক্টে অবশ্যই প্রয়োগ করুন।