Skip to content

Pretty message on exceptions from REST app

I have a problem with my REST app. Here is the git link to ease your burden: https://github.com/TheNiceGuy123/AstonishingBackEnd

Here is the raw code also:

Classes posted in order:

Employee Controller:

package com.example.csmartbackend.controller;

import com.example.csmartbackend.model.Employee;
import com.example.csmartbackend.modeldto.EmployeeDto;
import com.example.csmartbackend.service.EmployeeService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;
import java.util.UUID;

@RestController
@RequestMapping("employee")
@RequiredArgsConstructor
public class EmployeeController
{
    private final EmployeeService employeeService;

    @GetMapping("find/{Cnp}")
    public ResponseEntity<Employee> findByCNP(@PathVariable("Cnp") String Cnp)
    {
        Employee employee = employeeService.findByCnp(Cnp);
        HttpHeaders header = new HttpHeaders();
        header.add("Desc","Getting employee by id.");
        return ResponseEntity.status(HttpStatus.OK).headers(header).body(employee);
    }
}

Employee Service:

package com.example.csmartbackend.service;

import com.example.csmartbackend.mapper.EmployeeMapper;
import com.example.csmartbackend.model.Employee;
import com.example.csmartbackend.modeldto.EmployeeDto;
import com.example.csmartbackend.repository.EmployeeRepository;
import exception.general.TargetNotFoundException;
import exception.requestsexceptions.CNPNotFoundException;
import exception.requestsexceptions.EmployeeNotFoundException;
import exception.requestsexceptions.InvalidIdException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

@Service
@RequiredArgsConstructor
@Transactional
public class EmployeeService implements EmployeeServiceImpl
{
    private final EmployeeRepository employeeRepository;
    private final ContractService contractService;
    private final AdressService adressService;

    public Employee findByCnp(String Cnp) throws CNPNotFoundException
    {
        Optional<Employee> employeeOptional = Optional.ofNullable(employeeRepository.findByCnp(Cnp));
        if(employeeOptional.isPresent())
            return employeeOptional.get();
        else
            throw new CNPNotFoundException("No employee found.");
    }
}

Global Exception Handler:

package exception.requestsexceptions;

import org.springframework.beans.TypeMismatchException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingPathVariableException;
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.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler
{
    @Override
    protected ResponseEntity<Object> handleHttpRequestMethodNotSupported
            (HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request)
    {
        String message = ex.getMessage();
        List<String> details = new ArrayList<>();
        details.add("Request method not supported.");
        ApiErrorModel apiErrorModel = new ApiErrorModel(message, details, status, LocalDateTime.now());
        return ResponseEntity.status(status).body(apiErrorModel);
    }

    @Override
    protected ResponseEntity<Object> handleHttpMediaTypeNotSupported
            (HttpMediaTypeNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request)
    {
        String message = ex.getMessage();
        List<String> details = new ArrayList<>();
        details.add("Media method not supported.");
        ApiErrorModel apiErrorModel = new ApiErrorModel(message, details, status, LocalDateTime.now());
        return ResponseEntity.status(status).body(apiErrorModel);
    }

    @Override
    protected ResponseEntity<Object> handleMissingPathVariable
            (MissingPathVariableException ex, HttpHeaders headers, HttpStatus status, WebRequest request)
    {
        String message = ex.getMessage();
        List<String> details = new ArrayList<>();
        details.add("Path variable is missing.");
        ApiErrorModel apiErrorModel = new ApiErrorModel(message, details, status, LocalDateTime.now());
        return ResponseEntity.status(status).body(apiErrorModel);
    }

    @Override
    protected ResponseEntity<Object> handleMissingServletRequestParameter
            (MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request)
    {
        String message = ex.getMessage();
        List<String> details = new ArrayList<>();
        details.add("Request parameter is missing.");
        ApiErrorModel apiErrorModel = new ApiErrorModel(message, details, status, LocalDateTime.now());
        return ResponseEntity.status(status).body(apiErrorModel);
    }

    @Override
    protected ResponseEntity<Object> handleTypeMismatch
            (TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request)
    {
        String message = ex.getMessage();
        List<String> details = new ArrayList<>();
        details.add("Mismatch of type.");
        ApiErrorModel apiErrorModel = new ApiErrorModel(message, details, status, LocalDateTime.now());
        return ResponseEntity.status(status).body(apiErrorModel);
    }

    @Override
    protected ResponseEntity<Object> handleHttpMessageNotReadable
            (HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request)
    {
        String message = ex.getMessage();
        List<String> details = new ArrayList<>();
        details.add("Request body is not readable.");
        ApiErrorModel apiErrorModel = new ApiErrorModel(message, details, status, LocalDateTime.now());
        return ResponseEntity.status(status).body(apiErrorModel);
    }

    @ExceptionHandler(EmployeeNotFoundException.class)
    public ResponseEntity<Object> handleEmployeeNotFoundException(EmployeeNotFoundException ex)
    {
        String message = ex.getMessage();
        List<String> details = new ArrayList<>();
        details.add("Employee not found.");
        ApiErrorModel apiErrorModel = new ApiErrorModel(message, details, HttpStatus.BAD_REQUEST, LocalDateTime.now());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(apiErrorModel);
    }

    @ExceptionHandler(CNPNotFoundException.class)
    public ResponseEntity<Object> handleInvalidCNPException(CNPNotFoundException ex)
    {
        String message = ex.getMessage();
        List<String> details = new ArrayList<>();
        details.add("Employee with the given CNP not found.");
        ApiErrorModel apiErrorModel = new ApiErrorModel(message, details, HttpStatus.NOT_FOUND, LocalDateTime.now());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(apiErrorModel);
    }
}

CNPNotFoundException class:

package exception.requestsexceptions;

public class CNPNotFoundException extends RuntimeException
{
    public CNPNotFoundException(String message) { super(message); }
}

ApiErrorModel class:

package exception.requestsexceptions;

import lombok.*;
import org.springframework.http.HttpStatus;

import java.time.LocalDateTime;
import java.util.List;

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class ApiErrorModel
{
    String message;
    List<String> details;
    HttpStatus status;
    LocalDateTime timestamp;
}

I want to give some relevant informations to the user like in the second picture but in postman everything remains at the default level to call it like that, like my code from the handler is not even there. Is any logic or annotation that I miss?

This is how it looks on my side

This is how it should look like

Thank you for your time!

Answer

Problem is when a class is annotated with the @SpringBootApplication without any attribute SpringBoot only scans the package and its subpackages where the class itself is located.

In your case it means SpringBoot only looking for any Spring components under com.example.csmartbackend package while you put GlobalExceptionHandler class in exception.requestsexceptions package.

To fix it you have two choices:

  • put the exception package under com.example.csmartbackend, so it would be com.example.csmartbackend.exception.requestsexception.GlobalExceptionHandler
  • tell spring to scan the exception package as well, either by using scanBasePackageClasses or scanBasePackages attribute, e.g @SpringBootApplication(scanBasePackageClasses = {CSmartBackEndApplication.class, GlobalExceptionHandler.class})