Background
I have an error message class:
#XmlRootElement
public class ErrorMessage {
private String message;
public ErrorMessage() {
}
public ErrorMessage(String message) {
this.message = message;
}
public String getError() {
return message;
}
public void setError(String message) {
this.message = message;
}
}
This class has been assigned as a return value to an #ExceptionHandler in my Spring MVC REST controller:
#ExceptionHandler
#ResponseStatus(HttpStatus.NOT_FOUND)
#ResponseBody
ErrorMessage handleException(RuntimeException e) {
return new ErrorMessage("something went wrong");
}
Whenever the client triggers a RuntimeException after issuing a request with application/json as a Accept header, it receives a response with the correct status code and a matching JSON body:
{"error":"something went wrong"}
Alternatively, an XML body is received if the Accept header is application/xml:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<errorMessage><error>something went wrong</error></errorMessage>
Problem
Now, I would like to generify this solution by implementing a HandlerExceptionResolver instead so I don't have to copy / paste the #ExceptionHandler to every controller (or create a common "parent controller" that the other controllers can extend).
However, the AbstractHandlerExceptionResolver.doResolveException() method returns a ModelAndView and not my propriety ErrorMessage, so I tried the following:
public class RuntimeExceptionHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
#Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if (ex instanceof RuntimeException) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
ModelAndView mav = new ModelAndView();
mav.addObject("error", "something went wrong");
return mav;
}
return null;
}
}
When debugging, I can see that the mav.addObject() method is called. The response on the client side has the expected status code, but the content type is text/html with html in the body, rather than the JSON or XLM content that was specified by the Accept header in the original request.
(Side note, the actual exception, response code and text message in the example above is not important, they just serve as simple example.)
Spring version: 3.1.1.RELEASE
To get #ExceptionHandlers to use content negotiation, the AnnotationMethodHandlerExceptionResolver has a setMessageConverters() method which must be provided with the message converters, e.g.:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver">
<property name="messageConverters">
<list>
<ref bean="xmlConverter"/>
<ref bean="jsonConverter"/>
</list>
</property>
</bean>
But since you're using a custom method, you'll probably have to implement this functionality yourself. The simplest way is probably to Ctrl-C Ctrl-V from the AnnotationMethodHandlerExceptionResolver source, specifically the handleResponseBody() method.
I have spent some time investigating the matter and I have written a blog post in which I present a solution to the problem.
Update: If you are using Spring 3.2, you can take advantage of the #ControllerAdvice annotation. More details can be found in my second blog post.
Related
Consider the following mapping:
#RequestMapping(value = "/superDuperPage", method = RequestMethod.GET)
public String superDuperPage(#RequestParam(value = "someParameter", required = true) String parameter)
{
return "somePage";
}
I want to handle the missing parameter case by not adding in required = false. By default, 400 error is returned, but I want to return, let's say, a different page. How can I achieve this?
If a required #RequestParam is not present in the request, Spring will throw a MissingServletRequestParameterException exception. You can define an #ExceptionHandler in the same controller or in a #ControllerAdvice to handle that exception:
#ExceptionHandler(MissingServletRequestParameterException.class)
public void handleMissingParams(MissingServletRequestParameterException ex) {
String name = ex.getParameterName();
System.out.println(name + " parameter is missing");
// Actual exception handling
}
I want to return let's say a different page. How to I achieve this?
As the Spring documentation states:
Much like standard controller methods annotated with a #RequestMapping
annotation, the method arguments and return values of
#ExceptionHandler methods can be flexible. For example, the
HttpServletRequest can be accessed in Servlet environments and the
PortletRequest in Portlet environments. The return type can be a
String, which is interpreted as a view name, a ModelAndView object, a
ResponseEntity, or you can also add the #ResponseBody to have the
method return value converted with message converters and written to
the response stream.
An alternative
If you use the #ControllerAdvice on your class and if it extends the Spring base class ResponseEntityExceptionHandler. A pre-defined function has been created on the base class for this purpose. You have to override it in your handler.
#Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String name = ex.getParameterName();
logger.error(name + " parameter is missing");
return super.handleMissingServletRequestParameter(ex, headers, status, request);
}
This base class is very useful, especially if you want to process the validation errors that the framework creates.
You can do this with Spring 4.1 onwards and Java 8 by leveraging the Optional type. In your example that would mean your #RequestParam String will have now type of Optional<String>.
Take a look at this article for an example showcasing this feature.
Maybe not that relevant, but I came across to a similar need: change the 5xx error to 4xx error for authentication header missing.
The controller is as follows:
#RequestMapping("list")
public ResponseEntity<Object> queryXXX(#RequestHeader(value = "Authorization") String token) {
...
}
When you cURL it without the authorization header you get a 5xx error:
curl --head -X GET "http://localhost:8081/list?xxx=yyy" -H "accept: */*"
HTTP/1.1 500
...
To change it to 401 you can
#ExceptionHandler(org.springframework.web.bind.MissingRequestHeaderException.class)
#ResponseBody
public ResponseEntity<Object> authMissing(org.springframework.web.bind.MissingRequestHeaderException ex) {
log.error(ex.getMessage(), ex);
return IResponse.builder().code(401).message(ex.getMessage()).data(null).build();
}
#Data
public class IResponse<T> implements Serializable {
private Integer code;
private String message = "";
private T data;
...
}
You can verify it by an automation test:
#Test
void testQueryEventListWithoutAuthentication() throws Exception {
val request = get("/list?enrollEndTime=1619176774&enrollStartTime=1619176774&eventEndTime=1619176774&eventStartTime=1619176774");
mockMvc.perform(request).andExpect(status().is4xxClientError());
}
Here,my requirement is that i want separate code in my application for exception handling,i saw a nice option of spring there using #controller advice to handle exceptions globally.
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
}
But there i want to cutomization there,like proper dynamic messages,own error code. so how can i do this,i am new to spring boot and even i don't have knowledge of spring.Need basic example.
You can come up with a class like this to capture information to be sent in response in case of exception:-
public class APIResponse {
int errorCode;
String description;
String someInformation;
// any other information that you want to send back in case of exception.
}
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ResponseBody
#ExceptionHandler(DataIntegrityViolationException.class)
public APIResponse handleConflict(DataIntegrityViolationException exception) {
APIResponse response = createResponseFromException(exception);
return response;
}
}
In your controller advice class:-
Have the return type APIResponse instead of void.
The handler method can have the exception raised as the argument.
Using the exception object to create the APIResponse object.
Put #ResponseBody on the handler method.
Here is my fault interceptor:
public class OutFaultInterceptor extends AbstractPhaseInterceptor<Message> {
public OutFaultInterceptor() {
super(Phase.SEND);
}
#Override
public void handleMessage(Message message) throws Fault
{
Fault fault = (Fault)message.getContent(Exception.class);
Throwable ex = fault.getCause();
Response response = JAXRSUtils.convertFaultToResponse(ex, message);
message.setContent(Response.class, response);
}
}
Here is my relevant cxf-config:
<jaxrs:outFaultInterceptors>
<bean id="outfault" class="com.xxx.OutFaultInterceptor"/>
</jaxrs:outFaultInterceptors>
I can get into the handleMessage method no problem, but I'm not able to modify the message.
Currently what it returns is the default: ns1:XMLFault blah blah...
What I want to return is a Response object that has a proper HTTP response code and a json body (which I correctly have in my response variable above).
Thanks
If you're using JAX-RS, why not setup an exception mapper, and then use that mapper to handle the response.
A simple example:
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class MyExceptionMapper implements
ExceptionMapper<MyException> {
#Override
public Response toResponse(MyException e) {
return Response.status(Status.NOT_FOUND).build();
}
}
Then you would need to register the provider in the jaxrs serve by adding:
<jaxrs:providers>
<bean class="com.blah.blah.blah.blah.MyExceptionMapper"/>
</jaxrs:providers>
in the server config in the context. With that you have full access to the exception, and can get whatever you want from it.
I am using Spring MVC with Controllers, my question is how do I return a JSON response which is different from the #ResponseBody object which is returned and convereted to a JSON to be returned.
To elaborate further, I have the object called "UserDetails" which has two fields called "name", "emailAddress"
#ResponseBody UserDetails
now the json returned will look like
{ name : "TheUsersName",
emailAddress:"abc#abc123.com" }
Is there any way I can modify the json before returning (ALL jsons in all methods across all controllers) where a "status" field will be added and the other json data will be under the "data" key in the json.
Also how do I return a json to the frontend when the java server from somewhere throws an exception, the json should have "status : false" and the exception name (atleast the status part though)
Create a response class:
public class Response<T> {
T data;
boolean status = true;
public Response(T d) { data = d; }
}
Then return that from your controllers:
#ResponseBody public Response getUserDetails) {
//...
return new Response(userDetails);
}
For the exception you'll want to return an object like:
public class BadStatus {
String errorMessage;
boolean status = false;
public BadStatus(String msg) { errorMessage = msg; }
}
#ExceptionHandler(Exception.class)
public BadStatus handleException(Exception ex, HttpServletRequest request) {
return new BadStatus(ex.getMessage());
}
Yes. Return a model and a view instead.
public ModelMap getUserDetails() {
UserDetails userDetails; // get this object from somewhere
ModelMap map = new ModelMap()(;
map.addAttribute("data", userDetails);
map.addAttribute("success", true);
return map;
}
To add the exception you'd do it the same way with a key and success = false.
An alternate solution (works with spring 3.1), which is less invasive
in your spring config :
<bean id="jacksonConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="mypackage.MyMessageConverter"
p:delegate-ref="jacksonConverter">
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
The idea is to provide your own HttpMessageConverter that delegates to the provided jackson converter.
public class MyMessageConverter implements HttpMessageConverter<Object> {
// setters and delegating overrides ommitted for brevity
#Override
public void write(Object t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException {
// t is whatever your #ResponseBody annotated methods return
MyPojoWrapper response = new MyPojoWrapper(t);
delegate.write(response, contentType, outputMessage);
}
}
This way all your pojos are wrapped with some other json that you provide there.
For exceptions, the solution proposed by ericacm is the simplest way to go (remember to annotate the 'BadStatus' return type with #ResponseBody).
A caveat : your json-serialized BadStatus goes through MyMessageConverter too, so you will want to test for the object type in the overriden 'write' method, or have MyPojoWrapper handle that.
i configure my messageconverter as Jackson's then
class Foo{int x; int y}
and in controller
#ResponseBody
public Foo method(){
return new Foo(3,4)
}
from that i m expecting to return a JSON string {x:'3',y:'4'} from server without any other configuration. but getting 404 error response to my ajax request
If the method is annotated with #ResponseBody, the return type is written to the response HTTP body. The return value will be converted to the declared method argument type using HttpMessageConverters.
Am I wrong ? or should I convert my response Object to Json string myself using serializer and then returning that string as response.(I could make string responses correctly) or should I make some other configurations ? like adding annotations for class Foo
here is my conf.xml
<bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="jacksonMessageConverter"/>
</list>
</property>
You need the following:
Set annotation-driven programming model: put <mvc:annotation-driven /> in spring.xml
Place jaskson jar (Maven artifactId is org.codehaus.jackson:jackson-mapper-asl) in classpath.
Use as the following:
#RequestMapping(method = { RequestMethod.GET, RequestMethod.POST })
public #ResponseBody Foo method(#Valid Request request, BindingResult result){
return new Foo(3,4)
}
This works for me.
Please note, that
#ResponseBody is applied to return type, not to the method definition.
You need #RequestMapping annotation, so that Spring will detect it.
This worked for me:
#RequestMapping(value = "{p_LocationId}.json", method = RequestMethod.GET)
protected void getLocationAsJson(#PathVariable("p_LocationId") Integer p_LocationId,
#RequestParam("cid") Integer p_CustomerId, HttpServletResponse response) {
MappingJacksonHttpMessageConverter jsonConverter =
new MappingJacksonHttpMessageConverter();
Location requestedLocation = new Location(p_LocationId);
MediaType jsonMimeType = MediaType.APPLICATION_JSON;
if (jsonConverter.canWrite(requestedLocation.getClass(), jsonMimeType)) {
try {
jsonConverter.write(requestedLocation, jsonMimeType,
new ServletServerHttpResponse(response));
} catch (IOException m_Ioe) {
// TODO: announce this exception somehow
} catch (HttpMessageNotWritableException p_Nwe) {
// TODO: announce this exception somehow
}
}
}
Note that the method doesn't return anything: MappingJacksonHttpMessageConverter#write() does the magic.
The MessageConverter interface http://static.springsource.org/spring/docs/3.0.x/javadoc-api/ defines a getSupportedMediaTypes() method, which in case of the MappingJacksonMessageCoverter returns application/json
public MappingJacksonHttpMessageConverter() {
super(new MediaType("application", "json", DEFAULT_CHARSET));
}
I assume a Accept: application/json request header is missing.
A HTTP 404 error just means that the resource cannot be found. That can have 2 causes:
Request URL is wrong (client side error or wrong URL in given link/button).
Resource is not there where you expect it is (server side error).
To fix 1, ensure you're using or providing the correct request URL (casesensitive!). To fix 2, check the server startup logs for any startup errors and fix them accordingly.
This all goes beyond the as far posted code and information.
I found that I need jackson-core-asl.jar too, not only jackson-mapper-asl.jar
This is just a guess, but by default Jackson only auto-detects public fields (and public getters; but all setters regardless of visibility). It is possible to configure this (with version 1.5) to also auto-detect private fields if that is desired (see here for details).
I guess that 404 is not related to your HttpMessageConverter. I had same 404-issue and the reason was that I forgot that only requests matching <url-pattern> are sent to DispatcherServlet (I changed request mapping from *.do to *.json). Maybe this is your case also.
In addition to the answers here..
if you are using jquery on the client side, this worked for me:
Java:
#RequestMapping(value = "/ajax/search/sync")
public ModelAndView sync(#RequestBody Foo json) {
Jquery (you need to include Douglas Crockford's json2.js to have the JSON.stringify function):
$.ajax({
type: "post",
url: "sync", //your valid url
contentType: "application/json", //this is required for spring 3 - ajax to work (at least for me)
data: JSON.stringify(jsonobject), //json object or array of json objects
success: function(result) {
//do nothing
},
error: function(){
alert('failure');
}
});