5

Below is my controller method definition

@Autowired
private HttpServletRequest request;

@PostMapping(path = "/abc")
public String createAbc(@RequestBody HttpServletRequest request)
        throws IOException {

    logger.info("Request body: "+request.getInputStream());

    return "abc";

}

All i want to do is print contents to request body. But when i make a POST request i see below error:

Type definition error: [simple type, class javax.servlet.http.HttpServletRequest]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of javax.servlet.http.HttpServletRequest (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information\n at [Source: (PushbackInputStream); line: 1, column: 2]",

I'm using Spring boot 2.x version.Any idea what's wrong in my code?

bigdata123
  • 453
  • 2
  • 8
  • 24

2 Answers2

17

First, remove the @Autowired field. It's wrong and you're not using it anyway.

Now you have two choices:

  1. Let Spring process the request body for you, by using the @RequestBody annotation:

    @PostMapping(path = "/abc")
    public String createAbc(@RequestBody String requestBody) throws IOException {
        logger.info("Request body: " + requestBody);
        return "abc";
    }
    
  2. Process it yourself, i.e. don't use the @RequestBody annotation:

    @PostMapping(path = "/abc")
    public String createAbc(HttpServletRequest request) throws IOException {
        StringBuilder builder = new StringBuilder();
        try (BufferedReader in = request.getReader()) {
            char[] buf = new char[4096];
            for (int len; (len = in.read(buf)) > 0; )
                builder.append(buf, 0, len);
        }
        String requestBody = builder.toString();
        logger.info("Request body: " + requestBody);
        return "abc";
    }
    

Don't know why you'd use option 2, but you can if you want.

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • *remove the @Autowired field. It's wrong* - can you please explain why it is *wrong* ? Seen in https://stackoverflow.com/questions/3320674/spring-how-do-i-inject-an-httpservletrequest-into-a-request-scoped-bean – Scary Wombat Sep 12 '19 at 01:40
  • 1
    @ScaryWombat Well, true, it is wrong for a *singleton* bean. It is not wrong in that link you provided, since it explicitly says that it is a *request-scoped* bean. Since this question didn't specify that, the default type of bean is *singleton*, so it would be wrong. Besides, why create a *request-scoped* bean, when a singleton bean will suffice? Anyway, it is surely wrong to have `HttpServletRequest` as both an autowired field *and* as a parameter. – Andreas Sep 12 '19 at 01:48
  • *the default type of bean is singleton, so it would be wrong* - Thanks – Scary Wombat Sep 12 '19 at 01:53
  • 1
    @ScaryWombat Yeah, you have to add another annotation to change the scope, e.g. `@Scope(WebApplicationContext.SCOPE_REQUEST)`. Without a [`@Scope`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Scope.html) annotation, the bean is singleton. – Andreas Sep 12 '19 at 01:56
  • @Andreas one of the reasons you'd like to go for option 2 is when you don't receive the post as plain text but something like a gzip, that is sent by the client. You of course would not use the string builder to unzip the data but something like GZIPInputStream and dump it to a normal String, but that's one of the reasons why you would need to have access to HttpServletRequest. So, thanks for the answer mate, very helpful! – 0x4ndy Jul 04 '20 at 09:43
  • @4n0y If the content is gzipped, the request should have a header saying so, either `Content-Encoding: gzip` or `Transfer-Encoding: gzip`, and the unzipping should be applied by the framework *before* the data is received by the handler. With Spring, that does require a Filter, though. As such, you still wouldn't need to use option 2, which is a good thing, because repeating the unzipping and unmarshaling logic in every handler that needs to support gzip'd content would entirely fail the [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) principle. – Andreas Jul 04 '20 at 11:18
  • @Andreas Of course it should, but sometimes it doesn't, depending on where the request comes from and then you may want to handle the request differently. But regardless, the unzipping is not provided by the framework, and as you pointed out, you need to have a filter. Having access to HttpServletRequest in the controller, especially for a 2 additional lines of code to check for header and unzip is much more practical than providing a full-blown filter just for that - that's imho of course :-) – 0x4ndy Jul 04 '20 at 12:19
1

if you want to take it out easily:

private ObjectMapper objectMapper;

byte[] bytes = request.getInputStream().readAllBytes();

User u = objectMapper.readValue(bytes, User.class);