I use java spring.
I try to use ControllerAdvice in my project as a global event handler:
#ControllerAdvice
public class ExceptionsHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(RestClientException.class)
public void RestClientException(RestClientException ex)
{
System.out.println("RestClientException" + ex.getMessage());
}
#ExceptionHandler
public void Exception(Exception ex)
{
System.out.println("Exception" + ex.getMessage());
}
}
In this function I create and throw an exception:
#Component
#RequiredArgsConstructor
public class Scheduler {
public void schedule() throws JsonProcessingException, Exception {
throw new Exception();
}
}
And:
#Component
#RequiredArgsConstructor
public class RestfulReaderServiceImpl implements RestfulReaderService {
public String getData(String url) throws RestClientException {
throw new RestClientException();
}
}
But when those functions are fired and exceptions are thrown the exception handlers
in class ExceptionsHandler does not execute.
Any idea why exception handlers in ExceptionsHandler class not catches and handles the
exceptions?
#ControllerAdvice only handles exceptions thrown from Controller methods i.e. methods annotated with #RequestMapping or any of its shortcut annotations (#GetMapping etc.). You can check this by calling your exception throwing method from any #RequestMapping annotated method.
Related
I've created a service class to make an API call:
ServiceA {
public B sendReceive(Request req) {
return Optional.of(req).map(this::transform).map(apiService::call);
}
private ApiRequest transform(Request req) {
...
}
}
ApiService {
public B call(ApiRequest req) {
return webClient.post().bodyValue(req).bodyToMono().block();
}
#ControllerAdvice
ExceptionManager {
#ExceptionHandler(Exception.class)
public ResponseEntity handle(Exception ex) {
...
}
}
I tried to produce an exception in the transform method to test the ControllerAdvice class, but it does not work. The exception goes directly into InvocableHandlerMethod class instead of ExceptionManager class, which is inside the reactive package.
Since I'm not using any Mono/Flux, the typical .onError will not work for my case.
How do I make the ControllerAdvice working? Or how should I handle exception in the blocking call?
I have made some subclass for exception handling. The method throws a subclass exception. I have made #ExceptionHandler for both super class as well as subclasses. But only if there is no handling for super class of exception (handleSuperException(SuperclassExceptionexception e)), then the subclass exceptions are handled. SubClassAException, SubClassBException, SubClassCException extends SuperclassExceptionexception.
public class Controller #PostMapping("/path/") {
public ResponseEntity<String> method() throws SuperclassException{
}
#ExceptionHandler(SuperclassException.class)
public ResponseEntity handleSuperException(SuperclassExceptionexception e) {
//hadle superclass related
}
#ExceptionHandler({SubClassAException.class, SubClassBException.class, SubClassCException.class})
public ResponseEntity handleSubClassException(SuperclassExceptionexception e) {
//handle more specific
}
But it never goes to handleSubClassException even if subclass exceptions are thrown.
Unable to reproduce!
Here is Minimal, Reproducible Example, tested with Spring Boot 2.2.0 (Spring 5.2.0).
package web.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
#Controller
public class FailController {
#GetMapping("/dmz/fail")
public String failSuper() {
throw new SuperclassException("failSuper()");
}
#GetMapping("/dmz/failA")
public String failA() {
throw new SubClassAException("failA()");
}
#GetMapping("/dmz/failB")
public String failB() {
throw new SubClassBException("failB()");
}
#ExceptionHandler(SuperclassException.class)
public ResponseEntity<?> handleSuperException(SuperclassException e) {
return ResponseEntity.badRequest().body("handleSuperException: " + e);
}
#ExceptionHandler({SubClassAException.class, SubClassBException.class})
public ResponseEntity<?> handleSubClassException(SuperclassException e) {
return ResponseEntity.badRequest().body("handleSubClassException: " + e);
}
}
class SuperclassException extends RuntimeException {
public SuperclassException(String message) {
super(message);
}
}
class SubClassAException extends SuperclassException {
public SubClassAException(String message) {
super(message);
}
}
class SubClassBException extends SuperclassException {
public SubClassBException(String message) {
super(message);
}
}
I'm using /dmz/ so the Spring Security setup won't require login. In a plain vanilla Spring Boot setup, that will of course not be needed.
Output (http://localhost:8080/dmz/fail)
handleSuperException: web.controller.SuperclassException: failSuper()
Output (http://localhost:8080/dmz/failA)
handleSubClassException: web.controller.SubClassAException: failA()
Output (http://localhost:8080/dmz/failB)
handleSubClassException: web.controller.SubClassBException: failB()
Using 2.1.6.RELEASE
This is my serviceImpl class with repo.save method, in case of db field is duplicate we catch the exception and return in response
#Service
public class CoreVoucherServiceImpl implements CoreVoucherService {
#Override
#Transactional(propagation = REQUIRED)
public VoucherDTO createVoucher(VoucherDTO voucherDTO) {
... /* transforming DTO to Entity */
try {
voucherRepository.save(voucher);
} catch (Exception e) {
if (e.getCause() instanceof ConstraintViolationException) {
throw new MyException(FIELD_NOT_UNIQUE, "title");
}
UB_LOGGER.debug("Error in create voucher", e);
throw e;
}
voucherDTO.setId(voucher.getId());
return voucherDTO;
}
}
I am not able to add code coverage for the catch block. My Test class is
#SpringBootTest
#RunWith(SpringRunner.class)
public class CoreVoucherServiceTest {
#Autowired
private CoreVoucherService coreVoucherService;
#MockBean
private VoucherRepository voucherRepository;
#Test
// #Test(expected = MyException.class)
public void createVoucherTest() {
VoucherDTO dto = prepareCreateVoucher();
when(voucherRepository.save(any())).thenThrow(Exception.class);
coreVoucherService.createVoucher(dto);
}
}
with above way I am getting below error
org.mockito.exceptions.base.MockitoException:
Checked exception is invalid for this method!
Invalid: java.lang.Exception
How do I throw an Exception whose getCause is ConstraintViolationException so all lines are covered in testing
You have to test two use cases in your catch block:
When exception cause is ConstraintViolationException
.thenThrow(new RuntimeException(new ConstraintViolationException("Field not Unique", null, "title")));
When exception cause is not ConstraintViolationException
.thenThrow(new RuntimeException("oops"));
for this case #ExpectedException would be RuntimeException
You should throw the ConstraintViolationException, because save method does not throw any checked exception according to it's method definition save
when(voucherRepository.save(any()))
.thenThrow(.ConstraintViolationException.class);
You can also test exceptions with #Rule and ExpectedException in Junit.
#SpringBootTest
#RunWith(SpringRunner.class)
public class CoreVoucherServiceTest {
#Autowired
private CoreVoucherService coreVoucherService;
#MockBean
private VoucherRepository voucherRepository;
#Rule
public ExpectedException exceptionRule = ExpectedException.none();
#Test
// #Test(expected = MyException.class)
public void createVoucherTest() {
exceptionRule.expect(Exception.class); // Better if you specify specific Exception class that is going to be thrown.
VoucherDTO dto = prepareCreateVoucher();
when(voucherRepository.save(any())).thenThrow(Exception.class);
coreVoucherService.createVoucher(dto);
}
}
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
#Aspect
public class ExceptionAlerts {
public ExceptionAlerts() {
System.err.println("Class Scanned");
}
#AfterThrowing(pointcut = "com.name.papp.star", throwing = "ex")
public void doRecoveryAction(Throwable ex) throws Throwable {
System.err.println(">>>>>>>>>>>>>>>>Recovery Actions++++++++++++++++");
}
}
Service Interface
public interface SignInService {
CustomerSignInDTO signIn()
throws LappException;
}
Service Implementation Class
public class SignInServiceImpl implements SignInService {
#Override
#Transactional(readOnly = false, rollbackFor = Exception.class)
public CustomerSignInDTO signInCustomer(CustomerDeviceDTO customerDeviceDTO,
String mobileNumber, boolean createIfNotExist)
throws LappException {
// Throwing Exception Here
}
}
Problem -
Spring-Boot-1.2.5
The method doRecoveryActions never gets called. I am also using #ExceptionHandler somewhere to prepare the error response. Is it because #ExceptionHandler catches all the exceptions and doRecoveryActions is never called? Any suggestions would be appreciated!
now, I using the #ControllerAdvice in Spring 4.*.
using beforeBodyWrite method.
create custom annotation in controller class.
get the information of controller when #ControllerAdvice processing.
I want to know that request coming from what's controller class.
but, I don't know the solution.
any help.?
thanks
Although your question doesn't state clearly what is the purpose of what you are achieving and why do you need to create a custom annotation I'll post you some guidelines showing how to determine the source of your RuntimeException inside the ControllerAdvice
Given the following Rest controllers:
#RestController
public class CARestController {
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String testException() {
throw new RuntimeException("This is the first exception");
}
}
#RestController
public class CAOtherRestController {
#RequestMapping(value = "/test-other", method = RequestMethod.GET)
public String testOtherException() {
throw new RuntimeException("This is the second exception");
}
}
Which both throw an exception, we can capture this exception by using the following ControllerAdvice and determine the origin of the exception using the stack trace.
#ControllerAdvice
public class CAControllerAdvice {
#ExceptionHandler(value = RuntimeException.class)
protected ResponseEntity<String> handleRestOfExceptions(RuntimeException ex) {
return ResponseEntity.badRequest().body(String.format("%s: %s", ex.getStackTrace()[0].getClassName(), ex.getMessage()));
}
}
This is how the output of the endpoints will look:
My recommendation is that instead of doing this, you declare your own set of exceptions and then capture them in your controller advice, with independence of where they were thrown:
public class MyExceptions extends RuntimeException {
}
public class WrongFieldException extends MyException {
}
public class NotFoundException extends MyException {
}
#ControllerAdvice
public class CAControllerAdvice {
#ExceptionHandler(value = NotFoundException .class)
public ResponseEntity<String> handleNotFound() {
/**...**/
}
#ExceptionHandler(value = WrongFieldException .class)
public ResponseEntity<String> handleWrongField() {
/**...**/
}
}