By overriding the method 'handleMethodArgumentNotValid', you can customize the behavior of method argument invalid exceptions.
For example,
@ControllerAdvice(basePackages = "com.sample.app")
public class CustomErrorHandler extends ResponseEntityExceptionHandler {
	@Override
	protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
			HttpHeaders headers, HttpStatus status, WebRequest request) {
		List<String> errors = new ArrayList<String>();
		for (FieldError error : ex.getBindingResult().getFieldErrors()) {
			errors.add(error.getField() + " -> " + error.getDefaultMessage());
		}
		for (ObjectError error : ex.getBindingResult().getGlobalErrors()) {
			errors.add(error.getObjectName() + " -> " + error.getDefaultMessage());
		}
		ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, ex.getMessage(), errors);
		return handleExceptionInternal(ex, apiError, headers, apiError.getStatus(), request);
		// If you want to throw apiError directly, uncomment this
		// return new ResponseEntity(apiError, apiError.getStatus());
	}
}
I defined am employee class like below.
public class Employee {
	private int id;
	
	@NotNull(message = "Firstname can't be null")
	private String firstName;
	
	@NotNull(message = "LastName can't be null")
	private String lastName;
	....
	....
}
As you see the above definition, I am expecting firstName and lastName properties must not be null. If this property violates, Spring throws MethodArgumentNotValidException.
Follow below step-by-step procedure to build complete working application.
Step 1: Create new maven project 'spring-rest-custom-error-handler'.
Step 2: Update pom.xml with maven dependencies.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.sample.app</groupId>
	<artifactId>spring-rest-custom-error-handler</artifactId>
	<version>1</version>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.6.RELEASE</version>
	</parent>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>2.9.2</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
			<version>2.9.2</version>
		</dependency>
	</dependencies>
</project>
Step 3: Create application.yml file under src/main/resources folder.
application.yml
server:
  port: 1234
Step 4: Create a package 'com.sample.app.model' and define Employee, ApiError classes.
Employee.java
package com.sample.app.model;
import javax.validation.constraints.NotNull;
public class Employee {
	private int id;
	
	@NotNull(message = "Firstname can't be null")
	private String firstName;
	
	@NotNull(message = "LastName can't be null")
	private String lastName;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getFirstName() {
		return firstName;
	}
	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}
	public String getLastName() {
		return lastName;
	}
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
}
ApiError.java
package com.sample.app.model;
import org.springframework.http.HttpStatus;
public class ApiError {
	private HttpStatus status;
	private String message;
	private Object errors;
	public ApiError(HttpStatus status, String message, Object errors) {
		super();
		this.status = status;
		this.message = message;
		this.errors = errors;
	}
	public HttpStatus getStatus() {
		return status;
	}
	public void setStatus(HttpStatus status) {
		this.status = status;
	}
	public String getMessage() {
		return message;
	}
	public void setMessage(String message) {
		this.message = message;
	}
	public Object getErrors() {
		return errors;
	}
	public void setErrors(Object errors) {
		this.errors = errors;
	}
}
Step 5: Create a package ‘com.sample.app.exception.handler’ and define CustomErrorHandler class.
CustomErrorHandler.java
package com.sample.app.exception.handler;
import java.util.ArrayList;
import java.util.List;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
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.sample.app.model.ApiError;
@ControllerAdvice(basePackages = "com.sample.app")
public class CustomErrorHandler extends ResponseEntityExceptionHandler {
	@Override
	protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
			HttpHeaders headers, HttpStatus status, WebRequest request) {
		List<String> errors = new ArrayList<String>();
		for (FieldError error : ex.getBindingResult().getFieldErrors()) {
			errors.add(error.getField() + " -> " + error.getDefaultMessage());
		}
		for (ObjectError error : ex.getBindingResult().getGlobalErrors()) {
			errors.add(error.getObjectName() + " -> " + error.getDefaultMessage());
		}
		ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, ex.getMessage(), errors);
		return handleExceptionInternal(ex, apiError, headers, apiError.getStatus(), request);
		// If you want to throw apiError directly, uncomment this
		// return new ResponseEntity(apiError, apiError.getStatus());
	}
}
Step 7: Create a package ‘com.sample.app.config’ and define SwaggerConfig class.
SwaggerConfig.java
package com.sample.app.config;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Component
@EnableAutoConfiguration
@EnableSwagger2
public class SwaggerConfig {
	@Bean
	public Docket userApi() {
		return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().paths(PathSelectors.any())
				.apis(RequestHandlerSelectors.basePackage("com.sample.app.controller")).build();
	}
	private ApiInfo apiInfo() {
		return new ApiInfoBuilder().title("Query builder").description("Query builder using spring specification")
				.version("2.0").build();
	}
}
Step 8: Create a package ‘com.sample.app.controller’ and define EmployeeController.
EmployeeController.java
package com.sample.app.controller;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.sample.app.model.Employee;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@RestController
@RequestMapping("api/v1/employees")
@Api(tags = { "This section contains all Employee Speicifc Operations" })
public class EmployeeController {
	private static AtomicInteger counter = new AtomicInteger();
	private static Map<Integer, Employee> internalCache = new ConcurrentHashMap<>();
	@RequestMapping(method = RequestMethod.POST)
	@ApiOperation(value = "Create new employee", notes = "Create new employee")
	public ResponseEntity<Employee> create(@RequestBody @Valid Employee employee) {
		int newId = counter.incrementAndGet();
		employee.setId(newId);
		internalCache.put(newId, employee);
		return ResponseEntity.status(HttpStatus.CREATED).body(employee);
	}
}
Step 9: Define App class in com.sample.app package.
App.java
package com.sample.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App {
	public static void main(String args[]) {
		SpringApplication.run(App.class, args);
	}
}
Total project structure looks like below.
Run App.java.
Open the url ‘http://localhost:1234/swagger-ui.html’ and work with the api ‘POST /api/v1/employees’ with the below payload.
{
"lastName": "PTR"
}
You will see following error message in swagger response body.
{
  "status": "BAD_REQUEST",
  "message": "Validation failed for argument [0] in public org.springframework.http.ResponseEntity<com.sample.app.model.Employee> com.sample.app.controller.EmployeeController.create(com.sample.app.model.Employee): [Field error in object 'employee' on field 'firstName': rejected value [null]; codes [NotNull.employee.firstName,NotNull.firstName,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [employee.firstName,firstName]; arguments []; default message [firstName]]; default message [Firstname can't be null]] ",
  "errors": [
    "firstName -> Firstname can't be null"
  ]
}
Now pass empty object {} as request payload, you will see following error message.
{
  "status": "BAD_REQUEST",
  "message": "Validation failed for argument [0] in public org.springframework.http.ResponseEntity<com.sample.app.model.Employee> com.sample.app.controller.EmployeeController.create(com.sample.app.model.Employee) with 2 errors: [Field error in object 'employee' on field 'firstName': rejected value [null]; codes [NotNull.employee.firstName,NotNull.firstName,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [employee.firstName,firstName]; arguments []; default message [firstName]]; default message [Firstname can't be null]] [Field error in object 'employee' on field 'lastName': rejected value [null]; codes [NotNull.employee.lastName,NotNull.lastName,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [employee.lastName,lastName]; arguments []; default message [lastName]]; default message [LastName can't be null]] ",
  "errors": [
    "firstName -> Firstname can't be null",
    "lastName -> LastName can't be null"
  ]
}
You can download complete working application from this link.

This comment has been removed by the author.
ReplyDelete