Thursday 11 March 2021

Spring boot rest: handle MethodArgumentTypeMismatchException

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



Previous                                                    Next                                                    Home

No comments:

Post a Comment