2

I would like to set the content-length response header to one of my controllers as it is needed. After referring these two questions (First, Second), I can able to understand that setting response header would be critical. But, I need this for sure. My code snippet is as follows,

@GetMapping(value = /employees, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, List<Object>>> getEmployeeDetails(@RequestParam(value = "organization_id", required = false) Integer orgId) {

      Map<String, List<Object>> responseMap = //some method returns the data
      ObjectMapper objMapper = new ObjectMapper();
      String responseString = objMapper.writeValueAsString(responseMap);
      HttpHeaders httpHeaders = new HttpHeaders();
      httpHeaders.add(HttpHeaders.CONTENT_LENGTH, String.valueOf(responseString.length()));
      return ResponseEntity.ok().headers(httpHeaders).body(responseMap);
}

In the above case, Content-Length is not calculated correctly and the response json is shrinked. (i.e if the map contains 50 objects, response is shrinked somewhere inbetween)

Please help how to achieve the expected outcome. Thanks in advance.

2
  • 2
    There already is a solution in one of the questions you link, which you should use/prefer. If you really want to hack it like this use the spring created objectmapper not your own and write to a byte[] and count that length. This will however double the execution of your controller as you are serializing the payload twice (hence the recommendation for the other solution). Commented Jul 5, 2023 at 14:04
  • @M.Deinum Thanks for the comment. I have achieved it by modifying my code slightly. Commented Jul 13, 2023 at 14:29

2 Answers 2

1

I have done some modifications to the answer in one of the questions linked above. Below code gives me correct value for content-length header and my response json is served fully.

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;

import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;

@RestController
@RequestMapping("/rest")
public class EmployeeController {
    @GetMapping(value = /employees, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Map<String, List<Object>>> getEmployeeDetails(@RequestParam(value = "organization_id", required = false) Integer orgId) throws IOException {
        Map<String, List<Object>> responseMap = //some method returns the data
        ObjectMapper objMapper = new ObjectMapper();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        //getFactory() and createJsonGenerator() methods are deprecated
        JsonGenerator jsonGenerator = objMapper.getFactory().createGenerator(byteArrayOutputStream);
        objMapper.writeValue(jsonGenerator, responseMap);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add(HttpHeaders.CONTENT_LENGTH,String.valueOf(byteArrayOutputStream.size()));
        return ResponseEntity.ok().headers(httpHeaders).body(responseMap);
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

Don't create a new ObjectMapper inject the one configured by Spring Boot as that is eventually also the one that is going to be used to write the map and that can be configured differently then the one you create here.
Kindly, can you please post an answer or edit my answer as per your suggestion. I will try to implement it. Thanks.
1

Your code can be improved by doing 2 things:

  • use ObjectMapper from applicaiton context
  • use custom non-allocating OutputStream
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;

import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;

@RestController
@RequestMapping("/rest")
public class EmployeeController {
    @Autowired
    private ObjectMapper objMapper;

    @GetMapping(value = /employees, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Map<String, List<Object>>> getEmployeeDetails(@RequestParam(value = "organization_id", required = false) Integer orgId) throws IOException {
        Map<String, List<Object>> responseMap = //some method returns the data

        var countingOutputStream = new CountingOutputStream();
        objMapper.writeValue(countingOutputStream, responseMap);

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add(HttpHeaders.CONTENT_LENGTH,String.valueOf(countingOutputStream.count));
        return ResponseEntity.ok().headers(httpHeaders).body(responseMap);
    }
}

private static class CountingOutputStream extends OutputStream {
    private int count;

    @Override
    public void write(int b) {
        count++;
    }

    @Override
    public void write(byte[] b) {
        count += b.length;
    }

    @Override
    public void write(byte[] b, int off, int len) {
        Objects.checkFromIndexSize(off, len, b.length);
        count += len;
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.