MethodArgumentTypeMismatchException raised while resolving a controller method argument. This exception is thrown when the argument type is not matched to the expected type.
How to handle MethodArgumentTypeMismatchException?
@ControllerAdvice(basePackages = "com.sample.app")
public class CustomErrorHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler({ MethodArgumentTypeMismatchException.class })
public ResponseEntity<Object> handleMethodArgumentTypeMismatch(
MethodArgumentTypeMismatchException ex, WebRequest request) {
String error =
ex.getName() + " should be of type " + ex.getRequiredType().getName();
ApiError apiError =
new ApiError(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), error);
return handleExceptionInternal(ex, apiError, new HttpHeaders(), apiError.getStatus(), request);
// If you want to throw apiError directly, uncomment this
//return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
}
}
Find the below 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 javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
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.MissingServletRequestParameterException;
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.method.annotation.MethodArgumentTypeMismatchException;
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());
}
@Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
String error = ex.getParameterName() + " -> parameter is missing in request";
ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), error);
return handleExceptionInternal(ex, apiError, headers, apiError.getStatus(), request);
// If you want to throw apiError directly, uncomment this
// return new ResponseEntity<Object>(apiError, apiError.getStatus());
}
@ExceptionHandler({ ConstraintViolationException.class })
public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex, WebRequest request) {
List<String> errors = new ArrayList<String>();
for (ConstraintViolation<?> violation : ex.getConstraintViolations()) {
errors.add(violation.getRootBeanClass().getName() + " " + violation.getPropertyPath() + ": "
+ violation.getMessage());
}
ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), errors);
return handleExceptionInternal(ex, apiError, new HttpHeaders(), apiError.getStatus(), request);
// If you want to throw apiError directly, uncomment this
// return new ResponseEntity<Object>(apiError, new HttpHeaders(),
// apiError.getStatus());
}
@ExceptionHandler({ MethodArgumentTypeMismatchException.class })
public ResponseEntity<Object> handleMethodArgumentTypeMismatch(
MethodArgumentTypeMismatchException ex, WebRequest request) {
String error =
ex.getName() + " should be of type " + ex.getRequiredType().getName();
ApiError apiError =
new ApiError(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), error);
return handleExceptionInternal(ex, apiError, new HttpHeaders(), apiError.getStatus(), request);
// If you want to throw apiError directly, uncomment this
//return new ResponseEntity<Object>(apiError, new HttpHeaders(), 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.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.validation.ConstraintViolationException;
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.RequestParam;
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);
if (employee.getFirstName().equalsIgnoreCase(employee.getLastName())) {
throw new ConstraintViolationException("FirstName and LastName must not be equal", Collections.EMPTY_SET);
}
internalCache.put(newId, employee);
return ResponseEntity.status(HttpStatus.CREATED).body(employee);
}
@RequestMapping(method = RequestMethod.GET)
@ApiOperation(value = "Get employee by id", notes = "Get employee by id")
public ResponseEntity<Employee> create(@RequestParam(name = "empId", required = true) Integer empId) {
Employee emp = internalCache.get(empId);
if (emp == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
return ResponseEntity.status(HttpStatus.OK).body(emp);
}
}
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.
Execute following curl command in terminal, you will see the customized error message.
$curl -X GET "http://localhost:1234/api/v1/employees?empId=aa" -H "accept: */*"
{"status":"BAD_REQUEST","message":"Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer'; nested exception is java.lang.NumberFormatException: For input string: \"aa\"","errors":"empId should be of type java.lang.Integer"}
You can download the source code of this tutorial from below location.
https://github.com/harikrishna553/springboot/tree/master/rest/spring-rest-custom-error-handler
No comments:
Post a Comment