When I throw an exception around a function annotated with a CustomAnnotation, I get a response that looks like this:
{
"timestamp": "Jan 16, 2019 5:33:08 PM",
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/path"
}
But when I do this around #annotation(org.springframework.web.bind.annotation.RequestMapping), I receive a 401 status without a response body. Why is the response body missing and how do I work around this?
This is what the code looks like:
#Component
#Aspect
public class AnnotationProcessor {
#Around("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
// #Around("#annotation(path.to.my.CustomAnnotation)")
public Object proceed(ProceedingJoinPoint call) throws Throwable {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Unauthorized");
}
}
My objective is to validate incoming requests based on the annotations specified for the controller method.
If someone could suggest a solution/a better way to accomplish the same, that'd be great.
I would suggest to use ResponseEntity instead of throwing a status exception
public ResponseEntity<Void> proceed(ProceedingJoinPoint call){
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
You would be able to handle other HttpStatus on that ResponseEntity object, too.
Related
In my last project, there was a requirement for throwing exceptions when the request body contains extra parameters.
If the request will be like
{
"test":"body",
"name":"HR 1",
"location":"Location"
}
where test parameter is unnecessary and I've to return a response that should be like
{
"timestamp": "2022-05-07T00:13:59.144657",
"status": "500",
"error": "Internal Server Error",
"message": "test : must not be provided",
"path": "/api/departments/HR"
}
I've shared the answer. How I handled it.
In the application.properties I added this line.
spring.jackson.deserialization.fail-on-unknown-properties=true
This helps us to make deserialization fail on unknown properties and throw an exception which we can handle using handleHttpMessageNotReadable
create controller advice to handle exceptions
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return new ResponseEntity("Your Response Object", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
That's the solution.
So I have a REST controller and I'm trying to catch the message coming from the IllegalStateException
#GetMapping
public List<Student> getAllStudent(){
throw new IllegalStateException("Opps can not get all students");
//return studentService.getAllStudents();
}
In my front end I want to log the message "Opps can not get all students" When I use Postman I get this JSON response:
{
"timestamp": "2021-08-29T22:06:47.477+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/api/students"
}
How do I add a message attribute to the JSON so I can use it in my front end?
Throw an exception as you like (YourselfException) such as "Can not get all students Exception"
then you can define a GlobalExceptionHandler and you can return the message you want
For more information, you can try search "GlobalExceptionHandler"
#RestControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(value = YourselfException.class)
#ResponseBody
public Response bizExceptionHandler(YourselfException e){
return Response.fail(e.getMessage);
}
}
I'm writing a controller with Java for a webhook URL that receives a POST method call, with a JSON body, that I need to collect.
This is a simplified version of the controller, with Spring annotations:
#RestController
public class MyWebhoook {
#PostMapping("/my-webhook")
public void getMyJson(#RequestBody Map<String, Object> json) {
System.out.println("WebHook collected JSON: " + json);
}
}
I test it with Postman sending this JSON:
Header: Content-Type / application/json
{
"webhookKey" : "tranviaVermellCostaAvall",
"token" : "xx",
"channelId": 1,
"propertyId": "999999",
"status": "new",
"reservationId": "111211221",
"reservationStatus" : 1
}
And I get this answer:
{
"timestamp": "2019-04-09T07:23:38.093+0000",
"status": 405,
"error": "Method Not Allowed",
"message": "Request method 'POST' not supported",
"path": "/my-webhook"
}
The server log, gives some more information:
Request method 'POST' not supported, path=/my-webhook}] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#7756c3cd
I've tried those different approaches, with the same result:
#RequestMapping(value = "/my-webhook", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.POST) instead of the #PostMapping("/my-webhook") annotation.
Modelling the received JSON as a java object instead of the Map<String, Object> json.
Using HttpServletRequest instead of the #RequestBody annotation, trying to read the request as a String.
I do not understand the reason why the POST method is not allowed. Any help, would be very much appreciated.
I added {} between your method and got a 200 as return. I added the pictures below on what code, request and console output i got.
I copied your code to my spring boot app, worked perfectly via postman...
API:
#PostMapping("/my-webhook")
public void getMyJson(#RequestBody Map<String, Object> json) {
System.out.println("WebHook collected JSON: " + json);
}
RequestBody:
{
"webhookKey" : "tranviaVermellCostaAvall",
"token" : "xx",
"channelId": 1,
"propertyId": "999999",
"status": "new",
"reservationId": "111211221",
"reservationStatus" : 1
}
URL: http://localhost:8080/my-webhook
Try to:
Update and Clean your project.
Invalidate IDE cache and restart it, and try again.
The problem was with the CSRF (Cross-site request forgery) security configuration. The path of the webhook, has to be taken out of the CSRF control. Otherwise, the POST request doesn't pass the CSRF control.
This is a simplified extract of the security settings:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http)throws Exception{
http
.csrf()
.ignoringAntMatchers("/my/webhook")
How do I propogate an exception thrown in a call to a downstream service to the caller method?
I have a service that calculates something and throws an exception in case of error:
{
"timestamp": "2019-03-12T08:21:05.316+0000",
"status": 500,
"error": "Internal Server Error",
"message": "VAKUUTUSVUOSI.MAKSUEHTO is null or not numeric. Can't be added to due date.",
"path": "/rules/yk32/deducePaymentDueDate"
}
But the calling service displays this exception:
{
"timestamp": "2019-03-12T08:30:22.912+0000",
"status": 500,
"error": "Internal Server Error",
"message": "500 null",
"path": "/calculation/annual/payment"
}
How do I get the caller method also to display the message that the service throws "/rules/yk32/deducePaymentDueDate" instead of "Internal Server Error"?
Calling method:
LocalDate paymentDueDate = ykServiceAdapter.yk32DeducePaymentDueDate(requestDTO);
Calling function in the ykServiceadapter:
public LocalDate yk32DeducePaymentDueDate(Yk32RequestDTO requestDTO) {
ResponseEntity<LocalDate> re;
HttpEntity<?> entity = new HttpEntity<>(requestDTO);
try {
re = getRestTemplate().exchange(
buildServiceUrl(externalServiceConfig, RULE_YK32, DEDUCE_PAYMENT_DUEDATE),
HttpMethod.POST, entity,
new ParameterizedTypeReference<LocalDate>() {
});
return re.getBody();
} catch (HttpClientErrorException ex) {
if (HttpStatus.NOT_FOUND.equals(ex.getStatusCode())) {
return null;
} else {
throw ex;
}
}
}
You're working on two separate contexts, via HTTP.
What that means is the Exception generated by yk32DeducePaymentDueDate is transformed to an HTTP 500 response, which might mean the Exception message is used as response body.
Obviously, being that the original Exception gets lost during the HTTP call, RestTemplate is only able to create an HttpClientErrorException based on the HTTP status code
HttpClientErrorException.BadRequest
HttpClientErrorException.Conflict
HttpClientErrorException.Forbidden
HttpClientErrorException.Gone
HttpClientErrorException.MethodNotAllowed
HttpClientErrorException.NotAcceptable
HttpClientErrorException.NotFound
HttpClientErrorException.TooManyRequests
HttpClientErrorException.Unauthorized
HttpServerErrorException.InternalServerError
HttpServerErrorException.NotImplemented
...
In your case the instantiated Exception is
public static class InternalServerError extends HttpServerErrorException {
InternalServerError(String statusText, HttpHeaders headers, byte[] body, #Nullable Charset charset) {
super(HttpStatus.INTERNAL_SERVER_ERROR, statusText, headers, body, charset);
}
}
Only the Exception message might be recovered, if it has been transmitted in the response body.
You might want to look into a custom ResponseErrorHandler, where you can inspect the full HTTP response and react accordingly.
We have a microservice architecture. Each service exposing data through Rest. All controllers are set up using Spring:
#RestController
#RequestMapping(path = "foobar")
public class UiController {
#PostMapping("foo")
public ResponseEntity<Foo> addFoo(#RequestBody final FooDto fooDto) {
Foo fromDb = adminService.addFoo(converterToModel.convert(fooDto);
return ResponseEntity.ok(converterToDto.convert(fromDb));
}
If for some reason fooDto can't be added to the database. A custom Exception is thrown:
#ResponseStatus(value = HttpStatus.CONFLICT)
public class FooAlreadyAssignedException extends RuntimeException {
public FooAlreadyAssignedException(String msg) {
super("The following foos are already assigned to foobar: " + msg);
}
}
In Postman you see the following JSON after the Exception above is thrown
{
"timestamp": 1508247298817,
"status": 409,
"error": "Conflict",
"exception": "com.foo.exception.FooCodeAlreadyExistsException",
"message": "A foo with code: foo already exists",
"path": "/foo/foobar"
}
We have 4 different services like these all set up the same way.
Our UI is made in Angular 4 and makes REST calls to our Gateway. The Gateway is the connection between the microservices and the UI. It also exposes a REST endpoint. It's also implemented with Spring. I added a picture for clarification:
architecture
"edit: I see that I didn't complete the arrows. Of course all data is passed back up to the UI"
The problem
The Gateway uses a RestTemplate to call the APIs of the microservices
when a custom Exception is thrown in the microservice the Gateway returns this:
{
"timestamp": "2017-10-16T15:30:03.456Z",
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.web.client.HttpClientErrorException",
"message": "409 null",
"path": "/v1/printstations"
}
My original response a HttpStatus.conflict (status = 409) seems to be wrapped in a status 500 message by the Gateway. I don't want this behavior, I want it to pass the original message to the UI.
Any ideas on how to control this behavior?
Notes
I have tested with Postman that if you access the microservice directly it returns the 409 with the message written in the custom Exception
I have already tried overriding Springs ResponseErrorHandler but was not able to find a suitable solution that way.
In gateway code where spring rest template is calling your microservices, I would recommend catching HttpClientErrorException and then create your own exception class like ApiException as in below example, this way you will be able to pass the exact exception which is thrown from the microservices:
catch (org.springframework.web.client.HttpClientErrorException e) {
throw new ApiException(e.getMessage(), e, e.getRawStatusCode(), e.getResponseHeaders(),
e.getResponseBodyAsString(), fullURIPath, null);
}
where ApiException has a constructor like below:
public ApiException(String message, Throwable throwable, int code, Map<String, List<String>> responseHeaders,
String responseBody, String requestURI, String requestBody) {
super(message, throwable);
this.code = code;
this.responseHeaders = responseHeaders;
this.responseBody = responseBody;
this.requestURI = requestURI;
this.requestBody = requestBody;
}
Issue can be closed.
The solution was to map the exception that happened in the microservice to a valid ResponseEntity in the Gateway, so that the RestTemplate in the Gateway wouldn't repackage the error in a 500 server error.
We did this by creating a #ControllerAdvice class
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = {HttpClientErrorException.class})
protected ResponseEntity<Object> handleConflict(HttpClientErrorException ex, WebRequest request) {
return handleExceptionInternal(ex, ex.getResponseBodyAsString(),
new HttpHeaders(), ex.getStatusCode(), request);
}
}
This results in a ResponseEntity with the correct HttpStatus from the Exception in the microservice and a JSON body containing the message needed by the front-end.