54

This returns 200 OK with Content-Length: 0

@RestController
public class RepoController {
    @RequestMapping(value = "/document/{id}", method = RequestMethod.GET)
    public Object getDocument(@PathVariable long id) {
       return null;
    }

}

Simply put I'd like it to return 204 No Content on null.

Is there a way to force spring-mvc/rest to return 204 on null not 200? I dont want to change every rest method to return ResponseEntity or something like that, only map null to 204

user1606576
  • 1,062
  • 2
  • 11
  • 11

7 Answers7

99

You can use the @ResponseStatus annotation. This way you can have a void method and you don't have to build a ResponseEntity.

@DeleteMapping(value = HERO_MAPPING)
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long heroId) {
    heroService.delete(heroId);
}

BTW returning 200 when the object exists and 204 otherwise it's a bit unusual regarding API REST design. It's common to return a 404 (not found) when the requested object is not found. And this can be achieved using a ControllerAdvice.

In Spring REST it's better to handle Exceptions with a Exception handler instead of putting logic to decide the response status, etc. This is an example using the @ControllerAdvice annotation: http://www.jcombat.com/spring/exception-handling-in-spring-restful-web-service

spekdrum
  • 1,559
  • 2
  • 11
  • 15
  • 3
    I think the OP wanted to return 200 on success, and 204 if he had null. You can put anything you want in the ResponseStatus. But the idea was to have multiple possible HTTP codes for one endpoint. – Jean-François Beauchef Apr 09 '17 at 15:06
  • I agree with @Jean-FrançoisBeauchef. This is not the correct handling. This response status is applied irrespective of outcome (excluding exception scenario). The questioner wanted to know how to cater to condition of `null` – Karthik R Apr 19 '17 at 08:05
  • Sorry but I disagree. When using Spring REST it's better to handle exceptions and response codes with an Exception handler. This is an example http://www.jcombat.com/spring/exception-handling-in-spring-restful-web-service – spekdrum Apr 19 '17 at 08:48
  • 3
    @spekdrum yes of course. You should use an exception handler to handle an exception. But this is not what the OP was asking for. He wanted to return 200 on a correct response with content, and 204 (which is also a correct response) when there is no content (or null, if you prefer). The OP doesn't even mention exceptions. And in Java (Spring or otherwise), exceptions should not be used for flow control. – Jean-François Beauchef May 01 '17 at 22:39
  • Look at the answers from mahieus and Marek Raszewski. You will understand what the OP was shooting for. – Jean-François Beauchef May 01 '17 at 22:47
  • Im sorry but I have to say that what I'm suggesting is far different from using exceptions for flow control. Furthermore I still think it's a pretty valid answer. Not always we respond what the OP exactly asks, even more when we think that a better aproach can be done. I mean, when you call a GET method to a API it's common to asume that the element exists, throwing an exception otherwise. – spekdrum May 02 '17 at 13:29
  • Maybe this is not the "correct" null handling, but this is what Jersey does, and this is the idiom we settled on using. – user1606576 Jul 10 '20 at 08:24
39

Of course yes.

Option 1 :

@RestController
public class RepoController {
    @RequestMapping(value = "/document/{id}", method = RequestMethod.GET)
    public Object getDocument(@PathVariable long id, HttpServletResponse response) {
       Object object = getObject();
       if( null == object ){
          response.setStatus( HttpStatus.SC_NO_CONTENT);
       }
       return object ;
    }
}

Option 2 :

@RestController
public class RepoController {
    @RequestMapping(value = "/document/{id}", method = RequestMethod.GET)
    public Object getDocument(@PathVariable long id) {
       Object object = getObject();
       if ( null == object ){
          return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
       }

       return object ;
    }
}

Might have typos, but you get the concept.

Kirill Kulakov
  • 10,035
  • 9
  • 50
  • 67
Karthik R
  • 5,523
  • 2
  • 18
  • 30
  • 10
    Sadly, neither of these options is a good (clean) solution. Ideally, your controller method would return a typed object rather than `Object` or `ResponseEntity`, and there would be some sort of intercepter that would notice that you were returning null, and set the `NO_CONTENT` status code there (or was I just spoiled by Jersey and RESTEasy). – Shadow Man Sep 10 '18 at 22:55
15

I solved this problem with a filter. It's global and simple.

package your.package.filter;

import org.springframework.http.HttpStatus;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class NoContentFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        filterChain.doFilter(httpServletRequest, httpServletResponse);
        if (httpServletResponse.getContentType() == null ||
                httpServletResponse.getContentType().equals("")) {
            httpServletResponse.setStatus(HttpStatus.NO_CONTENT.value());
        }
    }
}

and add the following in your web.xml

<filter>
    <filter-name>restNoContentFilter</filter-name>
    <filter-class>your.package.filter.NoContentFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>restNoContentFilter</filter-name>
    <url-pattern>/rest/*</url-pattern>
</filter-mapping>
mahieus
  • 580
  • 3
  • 17
5

You can try this :

@RestController
public class RepoController {

    @RequestMapping(value = "/document/{id}", method = RequestMethod.GET)
    public ResponseEntity<String> getDocument(@PathVariable long id) {

       if(noError) {
           ............
           return new ResponseEntity<String>(HttpStatus.OK); 
       }
       else {
           return new ResponseEntity<String>(HttpStatus.BAD_REQUEST);
       }
   }
}

Uou need to change HttpStatus.BAD_REQUEST with the equivalent for 204 code status

Bilal BBB
  • 1,154
  • 13
  • 20
2

Question is old but for those that needs a global answer and have Spring 4+, you can create a ResponseBodyAdvice that changes response code base on the controller response. The following exemple do it for all @RestController classes :

@ControllerAdvice(annotations = { RestController.class })
public class NullToNoContentResponseBodyAdvice
    implements ResponseBodyAdvice<Object>
{
    /**
     * {@inheritDoc}
     */
    @Override
    public Object beforeBodyWrite(final Object p_responseBodyObject, final MethodParameter p_methodParameter,
                                  final MediaType p_mediaType, final Class<? extends HttpMessageConverter<?>> p_class,
                                  final ServerHttpRequest p_serverHttpRequest,
                                  final ServerHttpResponse p_serverHttpResponse)
    {
        // ------------------------- DECLARE -------------------------- //

        if (p_responseBodyObject == null)
        {
            p_serverHttpResponse.setStatusCode(HttpStatus.NO_CONTENT);
        }

        // Always return object unchanged or it will break response
        return p_responseBodyObject;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean supports(final MethodParameter p_methodParameter, final Class<? extends HttpMessageConverter<?>> p_class)
    {
        return AbstractGenericHttpMessageConverter.class.isAssignableFrom(p_class);
    }
}
1

Same answer but solved by AOP:

@Aspect
public class NoContent204HandlerAspect {

  @Pointcut("execution(public * xx.xxxx.controllers.*.*(..))")
  private void anyControllerMethod() {
  }

  @Around("anyControllerMethod()")
  public Object handleException(ProceedingJoinPoint joinPoint) throws Throwable {

    Object[] args = joinPoint.getArgs();

    Optional<HttpServletResponse> response = Arrays.asList(args).stream().filter(x -> x instanceof HttpServletResponse).map(x -> (HttpServletResponse)x).findFirst();

    if (!response.isPresent())
      return joinPoint.proceed();

    Object retVal = joinPoint.proceed();
    if (retVal == null)
      response.get().setStatus(HttpStatus.NO_CONTENT.value());

    return retVal;
  }
}
Marek Raki
  • 3,056
  • 3
  • 27
  • 50
  • Can you provide more detail on what is actually happening here. I'm trying to implement a similar solution. My idea is to automatically return 204 for all methods that have a void return type. It seems like this solution requires the method to take a HttpServletResponse as an input. I would like to avoid that. – mad_fox Nov 13 '17 at 16:43
  • Try to use a static way to retrieve HttpServletResponse like [here](https://stackoverflow.com/questions/20531204/why-there-is-not-a-holder-class-for-response-like-requestcontextholder) The rest should be the same but the @Pointcut should cover only void methods. – Marek Raki Nov 13 '17 at 23:34
0

you may custom yourself HttpMessageConverter to support this, as i do this , added spring.http.converters.preferred-json-mapper=gson toapplication.properties config file, and a result advice like :

@ControllerAdvice
public class CommonResultAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
    return true;
}

@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {

    if (null == o) {
    //set http code
        serverHttpResponse.setStatusCode(HttpStatus.NO_CONTENT);
        return BaseResult.success();
    }

    if (o instanceof String) {
        ObjectMapper mapper = new ObjectMapper();
        try {
            return mapper.writeValueAsString(BaseResult.success(o));
        } catch (JsonProcessingException ignore) {
        }
    }

    if (o instanceof BaseResult) {
        return o;
    }
    return BaseResult.success(o);
}

}

or custom a HttpMessageConverter like this:

@Configuration
public class BeanConfiguration {
@Bean
public HttpMessageConverter resultToJsonConverter() {
    return new GsonHttpMessageConverter();
}
}

hope to help you. :)

hai jiang
  • 199
  • 1
  • 4