【Spring MVC】REST APIにてエラーメッセージをカスタマイズする方法

概要

REST APIアプリにて、エラーメッセージをカスタマイズする方法についてまとめた。
以下の記事の続きとなる。

あわせて読みたい

概要 REST APIアプリにて、入力チェックを行った際のエラーハンドリング方法についてまとめた。 エラーメッセージはBean Validationのデフォルトメッセージを使用する。

【Spring MVC】REST APIにて基本的な入力チェックエラーハンドリングを行う方法

 

前提

動作確認はTalend API Testerを使用した。
Talend API Testerの使用方法については以下を参照。

あわせて読みたい

概要 GUIのツールを使用してシンプルにREAT APIアプリにリクエストを送りたいと思い、Talend API Testerを使用してみた。 いろいろと便利だったため、基本的な使用方法について簡単にまとめた。   事[…]

Talend API Testerを使用してREST APIにリクエストする方法

 

エラーメッセージをカスタマイズ

メッセージソースを使用して、外部ファイル(プロパティ)からメッセージ内容を取得する。
メッセージソースを使用するには、ResourceBundleMessageSourceまたはReloadableResourceBundleMessageSourceをBean登録する。
詳細は以下を参照。

あわせて読みたい

概要 Springのメッセージ管理を行うMessageSourceを使用して外部ファイルからメッセージを取得する方法についてまとめた。 MessageSourceはプロパティファイルに定義したメッセージを取得する機能を提供する。 […]

【Spring MVC】MessageSourceを使用する方法

 

メッセージソース

メッセージソースをDIするため、ルートアプリケーションコンテキストのBean定義ファイルにメッセージソース(ResourceBundleMessageSource)を定義する。

 

applicationContext.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
         		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-3.1.xsd">

	<!-- コンポーネントスキャン -->
	<context:component-scan
		base-package="com.example.rest_prototype.biz" />

	<!-- メッセージソース -->
	<!-- ResourceBundleMessageSourceを使用する場合 -->
	<bean id="messageSource"
		class="org.springframework.context.support.ResourceBundleMessageSource">
		<property name="basenames" value="ValidationMessages" />
	</bean>
</beans>

 

<property name=”basenames” value=”ValidationMessages” />

上記はメッセージソースが「ValidationMessages.properties」を参照することを意味する。
「ValidationMessages.properties」はクラスパス上(src/main/resources)に配置する。

 

メッセージプロパティ

メッセージプロパティにカスタマイズしたいメッセージを定義する。
プロパティの文字コードは「ISO-8859-1」とする。

 

ValidationMessages.properties


# デフォルトのValidationMessages.propertiesに定義
######################################
# プロパティ名
######################################
name=名前
id=リソースID
hogeDate=日付

######################################
# エラーメッセージ
######################################
# 汎用的なエラーメッセージ
NotNull.java.lang.String={0}を入力してください


 

エラーハンドリングクラス

入力チェックエラー時のメッセージをプロパティから取得する。

 

CustomExceptionHandler.java


package com.example.rest_prototype.web.advice;

import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

/**
 * 例外共通ハンドリングクラス
 *
 */
@ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {

	/** ValidationMessages.propertiesからカスタムメッセージの取得 */
	@Autowired
	private MessageSource messageSource;

	/**
	 * 入力チェックエラー(リクエストボディなど)
	 */
	@Override
	protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
			HttpHeaders headers, HttpStatus status, WebRequest request) {
		// エラー情報取得
		var errors = createInputError(ex.getBindingResult());

		return ResponseEntity.status(status).body(errors);
	}

	/**
	 * 入力チェックエラー(クエリパラメータなど)
	 */
	@Override
	protected ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status,
			WebRequest request) {
		// エラー情報取得
		var errors = createInputError(ex.getBindingResult());

		return ResponseEntity.status(status).body(errors);
	}

	/**
	 * エラー情報作成
	 * @param br
	 * @return
	 */
	private Map<String, String> createInputError(BindingResult br) {
		// フィールド名、デフォルトメッセージのマップ情報を返却
		var errors = new HashMap<String, String>();
		br.getFieldErrors()
		.stream()
		.forEach(e -> {
			// メッセージのキーを取得
			String messageKey = e.getCode() + "." + e.getField().getClass().getName();
			// フィールド名をメッセージキーに変換
			String fieldName = messageSource.getMessage(e.getField(), null, Locale.JAPANESE);
			// カスタムメッセージを取得
			String msg = messageSource.getMessage(messageKey, new Object[] { fieldName }, Locale.JAPANESE);
			errors.put(e.getField(), msg);
		});

		return errors;
	}
}

 

@Autowired
private MessageSource messageSource;

メッセージソースのDIを行う。

 

String messageKey = e.getCode() + “.” + e.getField().getClass().getName();

「NotNull.java.lang.String」という文字列を取得している。
上記はメッセージプロパティに定義した、エラーメッセージのひな型を定義しているメッセージキーとなる。

 

String fieldName = messageSource.getMessage(e.getField(), null, Locale.JAPANESE);

「名前」という文字列を取得している。
上記はメッセージプロパティに定義した、「name」に紐づく値を取得している。

 

String msg = messageSource.getMessage(messageKey, new Object[] { fieldName }, Locale.JAPANESE);

「名前を入力してください」という文字列を取得している。
上記は「NotNull.java.lang.String」とバインドする値(名前)を渡して、メッセージプロパティからメッセージ内容を取得している。

 

動作確認

サーバーを起動して、Talend API Testerなどを使用して動作確認を行う。

 

エラーメッセージ確認

nameフィールドをクエリパラメータに設定しない状態でリクエスト送信する。
メッセージプロパティから取得したエラーメッセージが表示された。

 

リクエスト


http://localhost:8080/rest_prototype/rest06/?hogeDate=2025-02-01

 

レスポンス


{"name":"名前を入力してください"}

 

エラーメッセージ実装例

ハンドリングしたエラーメッセージは様々カスタマイズが可能となる。
その他エラーメッセージの例を紹介する。

以下のようなエラー情報を返す例。

エラー情報


{
    "status": 400,
    "errorTitle": "入力エラー",
    "errorMsg": "入力に誤りあり",
    "errorCode": "SAMPLE_ERR0001",
    "errors": [
        {
            "target": "name",
            "msg": "名前を入力してください"
        },
        {
            "target": "id",
            "msg": "リソースIDを入力してください"
        }
    ]
}

 

このエラー情報では、以下のような汎用的なエラー情報を返却している。

・HTTPステータス
・エラータイトル概要
・エラーメッセージ概要
・エラーコード
・バリデーションフィールドエラー情報リスト

 

共通エラー情報クラス

共通するエラーフィールドを保持したクラス。
このフィールドをベースとして、サブクラスで必要に応じてエラー情報を追加する。

 

ErrorInfo.java


package com.example.rest_prototype.web.resources.error;

import lombok.Data;

/**
 * エラー情報クラス
 */
@Data
public class ErrorInfo {
	/** レスポンスステータス */
	private int status;
	/** エラータイトル概要 */
	private String errorTitle;
	/** エラーメッセージ概要 */
	private String errorMsg;
	/** エラーコード(特に用意していないので、あくまでサンプル) */
	private String errorCode;
}


 

バリデーションエラー情報クラス

共通エラー情報クラスを拡張した、バリデーションエラー情報クラス。
バリデーションエラー情報リストを追加で保持する。

 

ValidateErrorInfo.java


package com.example.rest_prototype.web.resources.error;

import java.util.ArrayList;
import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * 入力チェックエラー情報クラス
 */
@Data
public class ValidateErrorInfo extends ErrorInfo {

	/** 入力チェック項目別エラーリスト */
	private List<Detail> errors = new ArrayList<>();

	/**
	 * エラー詳細設定
	 * @param target
	 * @param msg
	 */
	public void addDetails(String target, String msg) {
		errors.add(new Detail(target, msg));
	}

	/**
	 * バリデーションエラー詳細
	 * 内部クラス
	 */
	@Data
	@AllArgsConstructor
	private static class Detail {
		/** 入力項目 */
		private String target;
		/** エラーメッセージ */
		private String msg;
	}

}


 

public class ValidateErrorInfo extends ErrorInfo {

ErrorInfo(共通エラー情報クラス)を継承して拡張している。

 

/** 入力チェック項目別エラーリスト */
private List<Detail> errors = new ArrayList<>();

バリデーションエラー詳細(Detailクラス)のリストを保持している。

 

private static class Detail {

バリデーションエラー詳細となる内部クラス。
エラーとなったフィールドとエラーメッセージ情報を保持する。

 

public void addDetails(String target, String msg) {

上記を呼び出すことで、バリデーションエラー詳細リストを追加していく。

 

エラーハンドリングクラス

作成したエラー情報をレスポンスとして返却する。

 

CustomExceptionHandler.java


package com.example.rest_prototype.web.advice;

import java.util.Locale;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import com.example.rest_prototype.web.resources.error.ValidateErrorInfo;

/**
 * 例外共通ハンドリングクラス
 *
 */
@ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {

	/** ValidationMessages.propertiesからカスタムメッセージの取得 */
	@Autowired
	private MessageSource messageSource;

	/**
	 * 入力チェックエラー(リクエストボディなど)
	 */
	@Override
	protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
			HttpHeaders headers, HttpStatus status, WebRequest request) {
		// エラー情報取得
		var errors = createInputError(ex.getBindingResult());

		return ResponseEntity.status(status).body(errors);
	}

	/**
	 * 入力チェックエラー(クエリパラメータなど)
	 */
	@Override
	protected ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status,
			WebRequest request) {
		// エラー情報取得
		var errors = createInputError(ex.getBindingResult());

		return ResponseEntity.status(status).body(errors);
	}

	/**
	 * エラー情報作成
	 * @param br
	 * @return
	 */
	private ValidateErrorInfo createInputError(BindingResult br) {
		var errorInfo = new ValidateErrorInfo();
		errorInfo.setStatus(HttpStatus.BAD_REQUEST.value());
		errorInfo.setErrorTitle("入力エラー");
		errorInfo.setErrorMsg("入力に誤りあり");
		errorInfo.setErrorCode("SAMPLE_ERR0001");

		br.getFieldErrors()
		.stream()
		.forEach(e -> {
			// メッセージのキーを取得
			String messageKey = e.getCode() + "." + e.getField().getClass().getName();
			// フィールド名をメッセージキーに変換
			String fieldName = messageSource.getMessage(e.getField(), null, Locale.JAPANESE);
			// カスタムメッセージを取得
			String msg = messageSource.getMessage(messageKey, new Object[] { fieldName }, Locale.JAPANESE);
			errorInfo.addDetails(e.getField(), msg);
		});

		return errorInfo;
	}
}

 

まとめ

 

☑ メッセージソースはBean登録を行って使用する

☑ メッセージ内容をメッセージプロパティに定義することで、任意のメッセージを取得することができる

☑ メッセージソースを利用することで、柔軟にエラーメッセージ内容をカスタマイズできる

 

スポンサーリンク