Implementing custom exceptions in Spring Boot - java

I have a simple spring boot application in which I have alot of tables. I have build their models, repositories, service and controller files. I have also tested all the apis through postman.
Now I need to implement custom exception in my models. Since I am at the beginning stage and learning things, I am a little confused as to how can I apply exceptions?
From what I have explored, I need to create three files
ErrorDetails.java
GlobalExceptionHandler.java
ResourceNotFoundException.java
Is this correct? If yes suppose I have added these files in my project . How do I implement these exceptions in my apis? Can anybody help me? Would mean alot. Thanks!

Whenever there is a case where resources will be not available then throw ResourceNotFoundException i.e. throw new ResourceNotFoundException("Error message of your choice");
For example in class CustomerTypeRepository within method getCustomerTypebyID instead of below code:
if (a == null) {
return ResponseEntity.notFound().build();
}
you can write
if (a == null) {
throw new ResourceNotFoundException("Customer type doesn't exist with the given id: "+Id);
}
and after that #ControllerAdvice GlobalExceptionHandler has already implamented for ResourceNotFoundException handler. So no need to worry about.

I believe in declaring checked exception as a contract, so I would do something like this
#Service
public class CustomerCategorizationService {
#Autowired
private CustomerTypeRepository customerTypeRepository;
// -------------- CustomerType API ----------------------- ///
public CustomerType saveCustomerType(CustomerType obj) throws ServiceException {
Where ServiceException is custom checked exception defined in application.
public CustomerType saveCustomerType(CustomerType obj) throws ServiceException {
//Other code
Long id;
try {
id = customerTypeRepository.save(obj);
}catch(DataAccessException cause) {
throw new ServiceException(cause.getMessage());
}
return id;
}
And in #ControllerAdvice
#ExceptionHandler(ServiceException.class)
public ResponseEntity<?> resourceNotFoundException(ServiceException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(),
request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
We can go one step further and throw custom exception from Controller class (say ResourceException) which will wrap ServiceException. In that case my #ControllerAdvice needs to only deal with ResourceException

Related

Java Spring Boot: Exception Handling

A little new to the Java Spring Boot flavor of webservices -- so please be gentle. Why do most Spring Boot Controller examples not show any exceptions being captured? I see some of my fellow developers do this a lot. And the answer is: it's a common convention. But why. Am I missing a key concept of web services created using Spring Boot?
For example:
#PostMapping(path = "/sampleEndpoint/v1/myEndpoint", produces = "application/json")
public ResponseEntity<String> myEndpoint(#RequestBody MyEndpointRequest myEndpointRequest) {
MyEndpointResponse response = someService.myEndpoint(myEndpointRequest);
return new ResponseEntity<>(response, HttpStatus.OK);
}
I would think, with respects to the architecture you would add AT LEAST a try/catch block with say some logging, and throw a new exception with the exceptions message:
#PostMapping(path = "/sampleEndpoint/v1/myEndpoint", produces = "application/json")
public ResponseEntity<String> myEndpoint(#RequestBody MyEndpointRequest myEndpointRequest) {
try{
MyEndpointResponse response = someService.myEndpoint(myEndpointRequest);
return new ResponseEntity<>(response, HttpStatus.OK);
}catch(Exception ex){
//Your favorite logger:
log.error("STACK_TRACE: {}", StaticClass.stackTraceToString(ex));
//throw exception for calling or consuming system/application:
throw new MiscException(ex.getMessage());
}
}
A couple of things to give context to this question (observation):
Use multiple data sources: a couple of databases, and some other web services (gives our client a one stop place to get their data.
Using this webservice with potentially 4 different client side /presentation layer type of applications.
My team would like to capture unexpected exceptions emanating from the data sources we tap into...and log them.
Well it's up to developer to implement catch of exceptions mechanism. But it's a good practise to define exceptions types and error codes/messages for that. Let's say you have an endpoint which fetch product with id, but there is no product with that id, in that case client will receive http 500 code with internal server error message. This will confuse users and also developers, what was the real cause of that error.
So prevent those, you can get help from #ControllerAdvice annotation, which will allow to apply exception handlers to more than one or all controllers.
First you will define your custom exceptions like :
public class ProductNotFoundException extends RuntimeException {
public ProductNotFoundException(Long id) {
super(String.format("Product with id %d not found", id));
}
}
and then you can define your ControllerAdvice class:
#ControllerAdvice
public class ExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(ProductNotFound.class)
public ResponseEntity<Object> handleProductNotFoundException(
ProductNotFoundException ex, WebRequest request) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("message", "Product not found");
return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);
}
}

Correct Pattern for handling Service Layer results

So I've been using Spring and Java for a while to build microservices. I am concerned by the way I am currently handling service layer results which uses "business exception"
Controller
#RestController
public class PurchaseController {
#Autowired
private PurchaseService purchaseService;
#PostMapping("/checkout")
public ResponseEntity<?> checkout(#RequestBody CheckoutRequest body) {
try {
SomeDTO dto = purchaseService.doCheckout(body);
return ResponseEntity.ok(dto);
}
catch (UnauthorizedException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
}
catch (CustomBusinessException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}
}
}
Service
#Service
public class PurchaseService {
// ...
public DTO doCheckout(CheckoutRequest request) {
// this one calls another microservice
if (!isUserValid(request.userId)) {
// current handling of business rules violation (1)
throw new UnauthorizedException("User not valid");
}
if (request.total < 10) {
// current handling of business rules violation (2)
throw new CustomBusinessException("Minimum checkout at 20 dollars");
}
// ... do actual checkout
return new DTO(someDTOData);
}
}
I was comfortable at using this "pattern" because I do not need to "if" the business result in the controller level to return the appropriate HttpStatusCode, but since I've found some articles saying that exception is expensive specifically in Java, I doubt what I was doing is good for the long run.
Is there another correct way to gracefully handles the business result layer?
The problem with ResponseEntity in Spring is that they are typed with the result object you want to return when the endpoint is called successfully, so you can't return another body different from the happy path one, that in your case would be SameDTO. One way to address this issue is to use ? as the type of the response entity, as you have done but it is not the most recommended way.
So the best way to do this is precisely to use exceptions when there is a situation when you can't return the expected object and you have to return another object or status code, but instead of using a try-catch in the controller you should use an exception handler (Controller Advice) https://www.baeldung.com/exception-handling-for-rest-with-spring.
This controller advice would catch any exception thrown in your application and depending on the exception type it could return a different response class or status code without affecting the main controller. One example of how can be your controller advice would be:
#ControllerAdvice
public class ErrorHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(RuntimeException.class)
public ResponseEntity<String> handleInternal(final RuntimeException ex) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ex.getMessage());
}
#ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<ResponseDto> identityClientException(UnauthorizedException e) {
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.body(e.getMessage());
}
#ExceptionHandler(CustomBusinessException.class)
public ResponseEntity<ResponseDto> identityClientException(CustomBusinessException e) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(e.getMessage());
}
And your controller woulb be much more clean without exception handling logic:
#RestController
public class PurchaseController {
#Autowired
private PurchaseService purchaseService;
#PostMapping("/checkout")
public ResponseEntity<SomeDTO> checkout(#RequestBody CheckoutRequest body){
SomeDTO dto = purchaseService.doCheckout(body);
return ResponseEntity.ok(dto);
}
}

How can I set up my application to ONLY return messages for ResponseStatusException?

By default, Spring Boot does not return messages for any exceptions, including ResponseStatusException, meaning that the message about bar below will not be returned to the client:
#GetMapping("/foo")
#ResponseBody
public Foo getFoo(#RequestParam(name = "bar", defaultValue = "0") int bar) {
if (bar <= 0) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "bar must always be positive");
}
return example.getFoo(bar);
}
This can be changed by setting server.error.include-message=always in the application.properties, however this causes ALL exception messages to be returned to the client, including this one:
#GetMapping("/baz")
#ResponseBody
public Baz getBaz() {
if (!security.checkSecurity()) {
throw new RuntimeException("Security breach! Hope no one finds out!");
}
return example.getBaz();
}
I know this is a trivial example and the solution would be just "don't throw server exceptions from your controller", but the exception might actually come from some other code buried deep in the application, it could even be a NullPointerException or whatever.
How can I get the application to show messages only from ResponseStatusException and not other types of exception? (I guess other than adding try-catch clauses to every single controller method.)
You can add extra (#ExceptionHandler) methods to any controller to specifically handle exceptions thrown by request handling (#RequestMapping) methods in the same controller. Such methods can:
Handle exceptions without the #ResponseStatus annotation (typically predefined exceptions that you didn’t write)
Redirect the user to a dedicated error view
Build a totally custom error response
Controller advice allows you to use exactly the same exception handling techniques but apply them across the whole application, not just to an individual controller. You can think of them as an annotation-driven interceptor.
Any class annotated with #ControllerAdvice becomes a controller-advice and three types of method are supported:
Exception handling methods annotated with #ExceptionHandler.
Model enhancement methods (for adding additional data to the model) annotated with #ModelAttribute. Note that these attributes are not available to the exception handling views.
Binder initialization methods (used for configuring form-handling) annotated with
#InitBinder.
Solution:
#ControllerAdvice
public class RestControllerAdvice {
#ExceptionHandler(ResponseStatusException.class)
public ResponseEntity<String> handleStatusException(ResponseStatusException exception) {
throw exception;
}
#ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception exception) {
return new ResponseEntity<>("Exception", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Catch all the exceptions and in the catch block throw ResponseStatusException like in:
#GetMapping("/actor/{id}")
public String getActorName(#PathVariable("id") int id) {
try {
return actorService.getActor(id);
} catch (ActorNotFoundException ex) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Actor Not Found", ex);
}
}

What is the best way to handle exception in Spring Boot?

I am developing an application in Spring Boot. I was wondering what is the best way to handle exceptions. So following is my code,
ExceptionHandler.java
I am using #ControllerAdvice. What is the best to use? #ControllerAdvice or #RestControllerAdvice?
#ControllerAdvice
public class ExceptionHandler {
#ExceptionHandler(NotFoundException.class)
public ResponseEntity<ErrorResponse> notFound(NotFoundException notFoundException) {
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.NOT_FOUND, notFoundException.getMessage());
return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.NOT_FOUND);
}
}
And implemented service layer #Service as follows,
#Override
public User update(User newUser) throws NotFoundException {
Optional<User> user = userRepository.findById(newUser.getId());
if (!user.isPresent()) throw new NotFoundException("Record Not Found!");
return userRepository.save(newUser);
}
In Controller.java code is as follows,
#PutMapping
public User update(#RequestBody User user) {
User updatedUser = userService.update(user);
if (updatedUser == null) throw new SomeOtherException("Exception while Updating!");
return updatedUser;
}
So my questions are:
Is the above approach bad? Is it okay to throw exceptions in the service layer and will it catch automatically by #controlleradvice? Or need to throw only in the controller? I am seeking the best practice to handle exceptions.
The way you used is perfectly fine.
As #ControllerAdvice/#RestControllerAdvice is used to handle exceptions globally throughout the controllers [flow].
You can throw exceptions in #Service layel also. [That is the same thing]
ControllerAdvice will catch it.
You can customize your Exceptions your own way.
I also like your approach. Maybe you want to have a look at Lombok and #SneakyThrows, too: I find it unneccessary to have throws XYZ on the methods, when at the end you handle them globally anyway.

Handling Exception in Spring boot Application with Hibernate

I am building a REST API with Spring boot and DAO layer is implemented in Hibernate.I need to understand the correct way of throwing and handling Exception in the Application.Currently I am doing it in this way
#Repository
public class UserDaoImpl
{
public getAllUsers() throws Exception
{
//get All Users from DB
}
}
#Service
public class UserServiceImpl
{
public getAllUsers throws MyCustomException
{ try{
userDaoImpl.getAllUsers();
}
catch(Exception e)
{
throw MyCustomException();
}
}
}
and In Exception Mapper
#ControllerAdvice
public class ApplicationExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler({MyCustomException.class})
#ResponseBody
public ResponseEntity<?> handleCustomException(Exception e) {
log.error("", e);
Map<String, String> error = new HashMap<String, String>();
error.put("message", e.getMessage());
return new ResponseEntity<>(error, HttpStatus.NOT_ACCEPTABLE, MessageResource.getLogMessage("BAD_REQUEST_EXCEPTION"));
}
}
public class MyCustomException extends RuntimeException
{
///// ....
}
So I have added throws clause (throws Exception) in DAO layer and catch at service layer and wrap it in Custom Exception(unchecked exception) and do not propogate the exception at controller layer.
Is this correct ? or there is some better way?
I'd recommend you to have general #ExceptionHandler({Exception.class}) for all cases that you don't want to handle specifically.
Also it's okay to create separate exception classes for situations that require custom handling.
It depends on what do you want to achieve.
About your case. Exception in DAO layer does not necessary mean that request was wrong or did not provide correct parameters. It could be mapping problems, DB access problems and etc. So I would not wrap it to my custom exception, or at lest wrap in to general DataAccessException, make good logging around that and return some general error code to the client.

Categories