1

I'm working with Java, Jetty and Jersey 2.18 (latest for now) hosted on a Google App Engine.

Let say I have a service such that

@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/{userId}")
public Response getUser(@PathParam("userId") String userId)
{
    ...
}

When I do:

    return Response.ok()
            .entity(user)
            .build();

I correctly receive an application/json content-type and body. But when I do:

    return Response
            .status(404)
            .entity(new ResponseModel(100, "user not found"))
            .build();

same as for returning any 4XX or 5XX status, I receive a text/html content-type along with this HTML body:

<html>
  <head>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
    <title>404 Not Found</title>
  </head>
  <body text=#000000 bgcolor=#ffffff>
    <h1>Error: Not Found</h1>
  </body>
</html>

and not the object I put in .entity()

Edit: Here is my web.xml

<?xml version="1.0" encoding="utf-8"?>
<web-app
    version="2.5"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <servlet>
        <servlet-name>Jersey Web Application</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>jersey.config.server.provider.packages</param-name>
            <param-value>com.mypackage.services;org.codehaus.jackson.jaxrs</param-value>
        </init-param>
        <init-param>
            <param-name>jersey.config.server.provider.classnames</param-name>
            <param-value>
                org.glassfish.jersey.server.gae.GaeFeature;
                org.glassfish.jersey.server.mvc.jsp.JspMvcFeature;
                org.glassfish.jersey.media.multipart.MultiPartFeature;
            </param-value>
        </init-param>
        <init-param>
            <param-name>com.sun.jersey.config.feature.Trace</param-name>
            <param-value>true</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Jersey Web Application</servlet-name>
        <url-pattern>/rest/*</url-pattytern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>home.jsp</welcome-file>
    </welcome-file-list>

    <filter>
        <filter-name>ObjectifyFilter</filter-name>
        <filter-class>com.googlecode.objectify.ObjectifyFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>ObjectifyFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- Spring Security Filter -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml  </param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <filter>
        <filter-name>GlobalResponseFilter</filter-name>
        <filter-class>com.mypackage.GlobalResponseFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>GlobalResponseFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>everything</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection>
        <user-data-constraint>
            <transport-guarantee>CONFIDENTIAL</transport-guarantee>
        </user-data-constraint>
    </security-constraint>

    <!-- ** -->
    <!-- ** General session timeout in minutes -->
    <!-- ** -->

    <session-config>
        <session-timeout>1440</session-timeout>
    </session-config>    
</web-app>

ResponseModel is just a basic serializable java class :

import java.io.Serializable;


public class ResponseModel implements Serializable
{
    private static final long   serialVersionUID    = 1L;

    private int                 code;
    private Serializable        data;

    public ResponseModel()
    {
    }

    public ResponseModel(int code, Serializable data)
    {
        System.err.println("Code " + code + " : " + data);
        this.code = code;
        this.data = data;
    }

    public int getCode()
    {
        return code;
    }

    public void setCode(int code)
    {
        this.code = code;
    }

    public Serializable getData()
    {
        return data;
    }

    public void setData(Serializable data)
    {
        this.data = data;
    }
}
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
Maxime T
  • 848
  • 1
  • 9
  • 17
  • 2
    Doesn't look like a duplicate to me. The other question is "how to get an error at all", this is "how to control the MIME type after making an error" which is not covered by the other answers. – o11c Jun 26 '15 at 20:22
  • Exactly @o11c thank you – Maxime T Jun 29 '15 at 08:02
  • Usually when Java server returns such an error it mean that the URL is not recognized. Are you sure that request comes to your code (getUser method) and is it executed? – Kamen Stoykov Jul 22 '15 at 13:21
  • @KamenStoykov yes the call goes through my method and my code gets executed (I can actually see the logs and error logs in the google app engine logs) – Maxime T Jul 22 '15 at 13:39
  • I used the example with 404 but the result it the same if I use other kind of http codes such as 4XX or 5XX – Maxime T Jul 22 '15 at 13:47
  • 1
    Maybe your Jetty is configured to have custom error pages? Check your ${jetty.home)/contexts directory. https://wiki.eclipse.org/Jetty/Howto/Custom_Error_Pages – heenenee Jul 22 '15 at 15:37
  • Could you show us your web.xml as well? – hfhc2 Jul 23 '15 at 09:28
  • @user880772 added description of my ResponseModel java class – Maxime T Jul 23 '15 at 13:12
  • @heenenee are you sur I can access that config file(s) on Google App Engine ? – Maxime T Jul 23 '15 at 13:13
  • @hfhc2 added description of my web.xml – Maxime T Jul 23 '15 at 13:14
  • @AndreiI yes, I also have text/html content-type when doing **return Response.status(400).build();** or **return Response.status(401).build();** or any 4XX or 5XX So the problem is not that Jersey can't find my service. Thanks for your help. – Maxime T Jul 23 '15 at 13:57
  • I recommend that you remove those filters from the `web.xml` file and check whether it comes from there. Also check those providers for jersey (disable them) in order to make sure they are not the cause of your problem. – V G Jul 23 '15 at 14:20
  • Maybe the platform itself filters the response of your java application? Google writes in the app engine doc that they serve default error pages and you can customize error handling in the appengine-web.xml - https://cloud.google.com/appengine/docs/java/config/appconfig#Java_appengine_web_xml_Custom_error_responses – ceth Jul 24 '15 at 06:51
  • @AndreiI if I return a 4XX response in my filter using the HttpServletResponse it works perfectly good. Such as doing : httpResponse.setContentType("text/plain"); httpResponse.setStatus(200); httpResponse.getWriter().println("Go away with that OPTIONS method !"); return; – Maxime T Jul 24 '15 at 13:01
  • @ceth I think it comes from Jersey or from javax's Response class and not from the app-engine. The reference link you used is pointing to app-engine's specific errors (over_quota, dos_api_denial, dos_api_denial, ...) but not for 4XX or 5XX responses. – Maxime T Jul 24 '15 at 13:05
  • @MaximeT does it still not work, even when all filters have been commented out? – V G Jul 24 '15 at 13:27
  • Also, very important: try some elder versions of Jersey! – V G Jul 24 '15 at 13:28
  • Looking at the latest Jerysey documentation, here is something interesting : https://jersey.java.net/documentation/latest/representations.html#d0e6530 Problem still not solved... – Maxime T Jul 24 '15 at 13:50

1 Answers1

6

Can you try again with the following configuration:

<servlet>
    <servlet-name>API</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    ...
    <init-param>
        <param-name>jersey.config.server.response.setStatusOverSendError</param-name>
        <param-value>true</param-value>
   </init-param>
</servlet>

This flag defines if Jersey - when sending a 4xx or 5xx response status - uses ServletResponse.sendError(flag is false) or ServletResponse.setStatus (flag is true).

Calling ServletResponse.sendError usually resets the response entity and headers and returns a (text/html) error page for the status code.

Since you want to return an own custom error entity, you need to set this flag to true.

wero
  • 32,544
  • 3
  • 59
  • 84
  • Oh my god it seems to work !! Looking for more documentation about this configuration step, I found this page : https://jersey.java.net/documentation/latest/appendix-properties.html Please edit your answer, including more information and source and I'll accept it (considering this still works tomorrow morning when I'll wake up :-) ) Thank you @wero – Maxime T Jul 25 '15 at 00:34
  • Life save even in 2022, for those that are using the Jersey configuration through Java, you can add this to your Jersey configuration: `property(ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, true);` – BuzZin' Jan 04 '22 at 12:56