概要
これまではエラーをそのまま返却しており SpringBoot 側はハンドリングされていないエラーはすべて 500 エラーで返します
今回は特定のエラーをハンドリングしエラーレスポンスをカスタムする方法を紹介します
環境
- macOS 26.4.1
- openjdk 26.0.1
- SpringBoot 4.0.6
- jasypt-spring-boot 4.0.4
- jackson-databind 2.21.2
- gradle 9.5.1
- VSCode 1.121.0
- MySQL 9.6.0
- Redis 8.6.3
カスタムエラーの作成
まずはカスタムエラーを作成します
独自の Exception になります
- vim src/main/java/com/example/demo/exception/BusinessException.java
package com.example.demo.exception;
public class BusinessException extends RuntimeException {
private final String code;
public BusinessException(String code, String message) {
super(message);
this.code = code;
}
public String getCode() {
return code;
}
}
エラーレスポンス用の DTO の作成
レスポンス用の DTO を作成します
エラーレスポンスとして返却したい情報を管理します
- vim src/main/java/com/example/demo/exception/dto/ErrorResponse.java
package com.example.demo.exception.dto;
import java.time.LocalDateTime;
public class ErrorResponse {
private String code;
private String message;
private LocalDateTime timestamp;
public ErrorResponse(String code, String message, LocalDateTime timestamp) {
this.code = code;
this.message = message;
this.timestamp = timestamp;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
public LocalDateTime getTimestamp() {
return timestamp;
}
}
エラーハンドラの作成
これがメイン部分です
SpringBoot では @RestControllerAdvice を基本的に使います
このアノテーションがあるクラスを自動的に読み込みエラーハンドリングしてくれます
- vim src/main/java/com/example/demo/exception/GlobalExceptionHandler.java
package com.example.demo.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.example.demo.exception.dto.ErrorResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import java.time.LocalDateTime;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleBusinessException(BusinessException ex) {
return new ErrorResponse(ex.getCode(), ex.getMessage(), LocalDateTime.now());
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleException(Exception ex) {
return new ErrorResponse("INTERNAL_SERVER_ERROR", "予期しないエラーが発生しました", LocalDateTime.now());
}
}
サービス側修正
サービス側は作成したカスタム Exception を返却するようにします
- vim src/main/java/com/example/demo/service/UserService.java
package com.example.demo.service;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import com.example.demo.dto.RedisMessageRequest;
import com.example.demo.dto.UserRequest;
import com.example.demo.dto.UserResponse;
import com.example.demo.entity.User;
import com.example.demo.exception.BusinessException;
import com.example.demo.repository.UserRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private ObjectMapper objectMapper;
public void createUser(UserRequest req) {
// 例:ビジネスルール
if (userRepository.existsByEmail(req.getEmail())) {
throw new BusinessException("USER_ALREADY_EXISTS", "既に登録されています");
}
User user = new User();
user.setName(req.getName());
user.setEmail(req.getEmail());
userRepository.save(user);
// Redis キューにメッセージを JSON 形式で送信
try {
RedisMessageRequest message = new RedisMessageRequest();
message.setName(req.getName());
message.setEmail(req.getEmail());
String jsonMessage = objectMapper.writeValueAsString(message);
redisTemplate.convertAndSend("chat", jsonMessage);
} catch (Exception e) {
throw new RuntimeException("Failed to send Redis message", e);
}
}
public List<UserResponse> getAllUsers() {
List<User> users = new ArrayList<>();
userRepository.findAll().forEach(users::add);
return users.stream().map(this::toResponse).toList();
}
private UserResponse toResponse(User user) {
UserResponse response = new UserResponse();
response.setName(user.getName());
return response;
}
}
動作確認
-
./gradlew clean && ./gradlew bootRun --args='--jasypt.encryptor.password=xxx'
-
curl -XPOST http://localhost:8080/demo/add -d '{"name": "First", "email": "someemail5@someemailprovider.com"}' -H 'content-type: application/json'
{"code":"USER_ALREADY_EXISTS","message":"既に登録されています","timestamp":"2026-05-25T13:32:16.13214"}
最後に
SpringBoot でエラーハンドリングする方法を紹介しました
@RestControllerAdvice を使いましょう
0 件のコメント:
コメントを投稿