I'm trying to create a simple error handling project, that will give JSON with error data after receiving an error (for example 404, 422 or 500). I work with code from this site, but it's not working for me.
I actually have this two classes:
BasicController class
package com.mycompany.jsonerrorhandler;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;
/**
* Class to catch all exception
*/
public class BasicController
{
#ExceptionHandler (Exception.class)
#ResponseStatus (HttpStatus.INTERNAL_SERVER_ERROR)
public ModelAndView handleAllExceptions(Exception ex)
{
return new JsonError(ex.getMessage()).asModelAndView();
}
}
JsonError class
package com.mycompany.jsonerrorhandler;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJacksonJsonView;
import com.google.common.collect.ImmutableMap;
/**
* Class that defines what JSON Error looks like
*/
public class JsonError
{
private final String message;
public JsonError(String message)
{
this.message = message;
}
public ModelAndView asModelAndView()
{
MappingJacksonJsonView jsonView = new MappingJacksonJsonView();
return new ModelAndView(jsonView, ImmutableMap.of("error", message));
}
}
I wonder what I need to connect them and receive JSON (or maybe there is other solution for this problem).
Based on the like you provided, the JsonError class should contain the following:
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJacksonJsonView;
import com.google.common.collect.ImmutableMap;
public class JsonError
{
private final String message;
public JsonError(String message) {
this.message = message;
}
public ModelAndView asModelAndView() {
MappingJacksonJsonView jsonView = new MappingJacksonJsonView();
return new ModelAndView(jsonView, ImmutableMap.of("error", message));
}
}
Related
I'm trying to centralize the error handling in my spring boot app. Currently i'm only handling one potential exception (NoSuchElementException), this is the controller advice:
import java.util.NoSuchElementException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
#ControllerAdvice
public class ExceptionController {
#ExceptionHandler(NoSuchElementException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public DispatchError dispatchNotFound(NoSuchElementException exception) {
System.out.println("asdasdasd");
return new DispatchError(exception.getMessage());
}
}
And here's the service which throws the exceptions:
import java.util.List;
import com.deliveryman.deliverymanapi.model.entities.Dispatch;
import com.deliveryman.deliverymanapi.model.repositories.DispatchRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class DaoService {
#Autowired
DispatchRepository dispatchRepo;
public Dispatch findByShipmentNumber(long shipmentNumber) {
return dispatchRepo.findById(shipmentNumber).orElseThrow();
}
public List<Dispatch> findByUser(String user, String status) {
if(status == null) {
return dispatchRepo.findByOriginator(user).orElseThrow();
} else {
return dispatchRepo.findByOriginatorAndStatus(user, status).orElseThrow();
}
}
public Dispatch createDispatch(Dispatch dispatch) { //TODO parameter null check exception
return dispatchRepo.save(dispatch);
}
}
The problem is that once I send a request for an inexistent resource, the json message shown is the spring's default one. It should be my custom json error message (DispatchError).
Now, this is fixed by adding a #ResponseBody to the exception handler method but the thing is that I was using an old code of mine as reference, which works as expected without the #ResponseBody annotation.
Can someone explain me why this is happening?
Either annotate your controller advice class with #ResponseBody
#ControllerAdvice
#ResponseBody
public class ExceptionController {
...
or replace #ControllerAdvice with #RestControllerAdvice.
Tested and verified on my computer with source from your controller advice.
From source for #RestControllerAdvice
#ControllerAdvice
#ResponseBody
public #interface RestControllerAdvice {
...
Hence, #RestControllerAdvice is shorthand for
#ControllerAdvice
#ResponseBody
From source doc for #ResponseBody
Annotation that indicates a method return value should be bound to the
web response body. Supported for annotated handler methods.
Alternative using #ControllerAdvice only:
#ControllerAdvice
public class ExceptionHandlerAdvice {
#ExceptionHandler(NoSuchElementException.class)
public ResponseEntity<DispatchError> dispatchNotFound(NoSuchElementException exception) {
return new ResponseEntity<>(new DispatchError(exception.getMessage()), HttpStatus.NOT_FOUND);
}
}
I do have a theory on what's going on in your old app. With the advice from your question, and the error handler below, I can create a behaviour where the DispatchError instance appears to be returned by advice (advice is executed), but is actually returned by error controller.
package no.mycompany.myapp.error;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
#RestController
#RequiredArgsConstructor
public class ErrorHandler implements ErrorController {
private static final String ERROR_PATH = "/error";
private final ErrorAttributes errorAttributes;
#RequestMapping(ERROR_PATH)
DispatchError handleError(WebRequest webRequest) {
var attrs = errorAttributes.getErrorAttributes(webRequest, ErrorAttributeOptions.of(ErrorAttributeOptions.Include.MESSAGE));
return new DispatchError((String) attrs.get("message"));
}
#Override
public String getErrorPath() {
return ERROR_PATH;
}
}
Putting an implementation of ErrorController into classpath, replaces Spring's BasicErrorController.
When reinforcing #RestControllerAdvice, error controller is no longer in effect for NoSuchElementException.
In most cases, an ErrorController implementation that handles all errors, in combination with advice exception handlers for more complex exceptions like MethodArgumentNotValidException, should be sufficient. This will require a generic error DTO like this
package no.mycompany.myapp.error;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.util.Map;
#Data
#NoArgsConstructor
#JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiError {
private long timestamp = new Date().getTime();
private int status;
private String message;
private String url;
private Map<String, String> validationErrors;
public ApiError(int status, String message, String url) {
this.status = status;
this.message = message;
this.url = url;
}
public ApiError(int status, String message, String url, Map<String, String> validationErrors) {
this(status, message, url);
this.validationErrors = validationErrors;
}
}
For ErrorHandler above, replace handleError with this
#RequestMapping(ERROR_PATH)
ApiError handleError(WebRequest webRequest) {
var attrs = errorAttributes.getErrorAttributes(webRequest, ErrorAttributeOptions.of(ErrorAttributeOptions.Include.MESSAGE));
return new ApiError(
(Integer) attrs.get("status"),
(String) attrs.get("message"), // consider using predefined message(s) here
(String) attrs.get("path"));
}
Advice with validation exception handling
package no.mycompany.myapp.error;
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import java.util.stream.Collectors;
#RestControllerAdvice
public class ExceptionHandlerAdvice {
private static final String ERROR_MSG = "validation error";
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
ApiError handleValidationException(MethodArgumentNotValidException exception, HttpServletRequest request) {
return new ApiError(
HttpStatus.BAD_REQUEST.value(),
ERROR_MSG,
request.getServletPath(),
exception.getBindingResult().getFieldErrors().stream()
.collect(Collectors.toMap(
FieldError::getField,
FieldError::getDefaultMessage,
// mergeFunction handling multiple errors for a field
(firstMessage, secondMessage) -> firstMessage)));
}
}
Related config in application.yml
server:
error:
include-message: always
include-binding-errors: always
When using application.properties
server.error.include-message=always
server.error.include-binding-errors=always
When using Spring Data JPA, consider using the following setting for turning off a second validation.
spring:
jpa:
properties:
javax:
persistence:
validation:
mode: none
More information on exception handling in Spring:
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc (revised April 2018)
https://www.baeldung.com/exception-handling-for-rest-with-spring (December 31, 2020)
For example if I have the following controller:
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.*;
#Controller("/test")
public class TestController {
#Get()
#Produces(MediaType.TEXT_PLAIN)
public String index() {
// How should this be implemented?
return "???";
}
}
and I run it on my-server, then I would like the index method to return http://my-server:8080.
Asof Micronaut V1.2.0, you can use the HttpHostResolver interface, for example:
import io.micronaut.http.*;
import io.micronaut.http.annotation.*;
import io.micronaut.http.server.util.HttpHostResolver;
import io.micronaut.web.router.RouteBuilder;
#Controller("/test")
public class TestController {
private final HttpHostResolver httpHostResolver;
private final RouteBuilder.UriNamingStrategy uriNamingStrategy;
public TestController(
HttpHostResolver httpHostResolver,
RouteBuilder.UriNamingStrategy uriNamingStrategy
) {
this.httpHostResolver = httpHostResolver;
this.uriNamingStrategy = uriNamingStrategy;
}
#Get()
#Produces(MediaType.TEXT_PLAIN)
public String index(HttpRequest httpRequest) {
return httpHostResolver.resolve(httpRequest) +
uriNamingStrategy.resolveUri(TestController.class);
}
}
This seems to work:
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.*;
import io.micronaut.runtime.server.EmbeddedServer;
import io.micronaut.web.router.RouteBuilder;
import java.net.*;
#Controller("/test")
public class TestController {
protected final String baseUrl;
public TestController(EmbeddedServer embeddedServer, RouteBuilder.UriNamingStrategy uns)
throws MalformedURLException {
final String host = embeddedServer.getHost();
final int port = embeddedServer.getPort();
final String file = uns.resolveUri(TestController.class);
baseUrl = new URL("http", host, port, file).toString();
}
#Get()
#Produces(MediaType.TEXT_PLAIN)
public String index() {
return baseUrl;
}
}
I'm not sure whether it's idiomatic, or whether it works in all cases. If someone posts a better answer I'll accept that.
If you want the controller to respond at / then use #Controller("/") instead of #Controller("/test").
package com.tech.api;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.PathVariable;
import java.util.List;
import javax.inject.Inject;
#Controller("/")
public class ModelDefinitionsApi {
#Get(uri="/modelName", produces = MediaType.APPLICATION_JSON)
public String getModel(#PathVariable String modelName) {
return "modelName";
}
}
http://my-server:8080 => main controller url
http://my-server:8080/modelName => for getModel method
I have a post call with a
public Test postTest(#RequestBody Test test) {
}
public class Test {
#JsonProperty("EMAIL_ADDRESS")
#NotNull(message = "EMAIL_ADDRESS_NOT_NULL")
#Size(min = 1, message = "EMAIL_ADDRESS_NOT_EMPTY")
#Email(flags = Flag.CASE_INSENSITIVE, message = "EMAIL_INVALID")
private String emailAddress;
public ForgetPassword(
#NotNull(message = "EMAIL_ADDRESS_NOT_NULL") #Size(min = 1,
message = "EMAIL_ADDRESS_NOT_EMPTY") #Email(flags =
Flag.CASE_INSENSITIVE,
message = "EMAIL_INVALID") String emailAddress) {
super();
this.emailAddress = emailAddress;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
}
I was missing the default constructor and it kept returning bad request when I tried to make the post. If I add the default constructor it works. It does not even throw a stack trace. I am using a custom template to send back the response in. I'm overriding the BadRequestException to send back a custom message.
#ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {
public BadRequestException(String message) {
super(message);
}
}
Is there any method which I should override to catch such exceptions. I want to be able to know why something is going wrong and to be able to see a stack strace.
You can define Global Exceptional Handler using #ControllerAdvice. You can place as many handlers there using #ExcecptionHandler. You can log errorTrace for debugging purpose.
package com.startwithjava.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
#ControllerAdvice
public class ApiExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> handlerGenericError(Exception ex){
ex.printStackTrace();
return new ResponseEntity<>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(BadRequestException.class)
public ResponseEntity<Object> handlerBadRequest(BadRequestException ex){
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
}
You can declare a class like below to handle exceptions.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
#ControllerAdvice
public class GlobalExceptionHandler {
private static Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
#ExceptionHandler(Exception.class)
#ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR, reason="Error processing the request, please check the logs")
public void handleException(Exception e) {
LOGGER.error("Exception occurred", e);
}
}
Im trying to have java respond to a GET request from Postman. When I use Postman to send a GET request to localhost:8080/chat this is the response (I had expected to get an empty list returned as there is no data present yet:
{
"timestamp": "2018-04-02T20:00:26.413+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/chat"
}
I have 2 packages in my application. They are com.dogo and com.dogochat.chat. The file in com.dogo is DogoApplication.java. This is the code:
package com.dogo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class DogoApplication {
public static void main(String[] args) {
SpringApplication.run(DogoApplication.class, args);
}
}
The second package is called com.dogochat.chat. There are 3 files (2 classes and 1 interface). The file names are Message.java, MessageController.java, and MessageRepository.java.
This is the code in Message.java:
package com.dogochat.chat;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name="message")
public class Message {
#Id
#GeneratedValue
private Integer id;
private String name;
private String content;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
This is the code in MessageController.java:
package com.dogochat.chat;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/chat")
public class MessageController {
#Autowired
MessageRepository dao;
#GetMapping("/get")
public List<Message> getMessages(){
List<Message> foundMessages = dao.findAll();
return foundMessages;
}
#PostMapping("/post")
public ResponseEntity<Message> postMessage(#RequestBody Message message)
{
// saving to DB using instance of the repo interface
Message createdMessage = dao.save(message);
// RespEntity crafts response to include correct status codes.
return ResponseEntity.ok(createdMessage);
}
}
This is the code in MessageRepository.java (although I dont think this is needed for this small test)
package com.dogochat.chat;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MessageRepository extends JpaRepository<Message, Integer>{
}
Any help/suggestions are greatly appreciated.
Thanks.
I know I am pretty late for the response, but better late than never.
This is a very common mistake for new Spring boot developers and I have also faced this issue several times.
The reason for this is the package under which you have your main() method.
In your case your MessageController, Message and your MessageRepository is under the package com.dogochat.chat, but then your main() method is under com.dogo which is completely a different package.
#SpringBootApplication internally runs #ComponentScan and if parent package and child packages are different, it cannot run and scan and throws the above error.
To avoid this confusion, follow this package structure.
Hope this helps. Happy coding !
Check to see if your #SpringBootApplication class is on top of all your packages or at least in the same package. It seems like your URL path is not visible.
Also, in your PostMan, you have to configure your headers the content-type=application/json.
For a more readable code (just my point of view), you should have something like this :
package com.dogochat.chat;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/chat")
public class MessageController {
#Autowired
MessageRepository dao;
#GetMapping("/get")
public List<Message> getMessages(){
List<Message> foundMessages = dao.findAll();
return foundMessages;
}
#PostMapping("/post")
public ResponseEntity<Message> postMessage(#RequestBody Message message)
{
// saving to DB using instance of the repo interface
Message createdMessage = dao.save(message);
// RespEntity crafts response to include correct status codes.
return ResponseEntity.ok(createdMessage);
}
}
I am trying to create a simple web service which outputs using json, but am not getting the desired Json output.
POJO:
package com.rest.resource;
import java.io.Serializable;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Track implements Serializable
{
#XmlElement
String singer = "ABC";
#XmlElement
String title = "XYZ";
}
Service:
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.bind.JAXBException;
import com.rest.resource.Track;
#Path("/json/metallica")
public class JSONService
{
#POST
#Path("/post")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Track createTrackInJSON(final Track track)
{
return track;
}
#GET
#Path("/get")
#Produces(MediaType.APPLICATION_JSON)
public Response getTrackInJSON() throws JAXBException
{
final Track track = new Track();
return Response.status(201).entity(track).build();
}
}
On /get I get
{"singer":"ABC","title":"XYZ"}
but I want "track": {"singer":"ABC","title":"XYZ"}
I am unable yo print the root element.
I tried using a CustomJAXBContextResolver class but did not work for me? Can anyone give an example of the same?
If you want to use the ContextResolver, you'd need to use the JSONConfiguration and switch the JSON Notation. You could do that by adding a class like this:
#Provider
public class MyJAXBContextProvider implements ContextResolver<JAXBContext> {
private JSONJAXBContext trackCtx;
public MyJAXBContextProvider() throws JAXBException {
trackCtx = new JSONJAXBContext(JSONConfiguration.mappedJettison().build(), Track.class);
}
public JAXBContext getContext(Class<?> type) {
if(type == Track.class) {
return trackCtx;
}
return null;
}
}
Adding that class produced this for me:
{"track":{"singer":"ABC","title":"XYZ"}}
For more info check out the Jersey Docs
You'd have to wrap Track with another object:
public class TrackWrapper {
Track track;
TrackWrapper(Track track) {
this.track=track;
}
}
and return an instance of TrackWrapper,
#GET
#Path("/get")
#Produces(MediaType.APPLICATION_JSON)
public Response getTrackInJSON() throws JAXBException
{
final TrackWrapper trackWrapper = new TrackWrapper(new Track());
return Response.status(201).entity(trackWrapper).build();
}
}
and just in case, if you're gonna use JSON only you don't need the JAXB annotations.