I like the approach to Franck Lefebure, but I don't like to use reflection, so here is a solution using custom PrettyFormattedBody types + pretty formatted arrays / lists
Spring Configuration:
@Bean MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() { return new CustomJsonResponseMapper(); }
CustomJsonResponseMapper.java:
public class CustomJsonResponseMapper extends MappingJackson2HttpMessageConverter { private final ObjectMapper prettyPrintObjectMapper; public CustomJsonResponseMapper() { super(); prettyPrintObjectMapper = initiatePrettyObjectMapper(); } protected ObjectMapper initiatePrettyObjectMapper() { // clone and re-configure default object mapper final ObjectMapper prettyObjectMapper = objectMapper != null ? objectMapper.copy() : new ObjectMapper(); prettyObjectMapper.configure(SerializationFeature.INDENT_OUTPUT, true); // for arrays - use new line for every entry DefaultPrettyPrinter pp = new DefaultPrettyPrinter(); pp.indentArraysWith(new DefaultIndenter()); prettyObjectMapper.setDefaultPrettyPrinter(pp); return prettyObjectMapper; } @Override protected void writeInternal(final Object objectToWrite, final Type type, final HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { // based on: if objectToWrite is PrettyFormattedBody with isPretty == true => use custom formatter // otherwise - use the default one final Optional<PrettyFormattedBody> prettyFormatted = Optional.ofNullable(objectToWrite) .filter(o -> o instanceof PrettyFormattedBody) .map(o -> (PrettyFormattedBody) objectToWrite); final boolean pretty = prettyFormatted.map(PrettyFormattedBody::isPretty).orElse(false); final Object realObject = prettyFormatted.map(PrettyFormattedBody::getBody).orElse(objectToWrite); if (pretty) { // this is basically full copy of super.writeInternal(), but with custom (pretty) object mapper MediaType contentType = outputMessage.getHeaders().getContentType(); JsonEncoding encoding = getJsonEncoding(contentType); JsonGenerator generator = this.prettyPrintObjectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding); try { writePrefix(generator, realObject); Class<?> serializationView = null; FilterProvider filters = null; Object value = realObject; JavaType javaType = null; if (realObject instanceof MappingJacksonValue) { MappingJacksonValue container = (MappingJacksonValue) realObject; value = container.getValue(); serializationView = container.getSerializationView(); filters = container.getFilters(); } if (type != null && value != null && TypeUtils.isAssignable(type, value.getClass())) { javaType = getJavaType(type, null); } ObjectWriter objectWriter; if (serializationView != null) { objectWriter = this.prettyPrintObjectMapper.writerWithView(serializationView); } else if (filters != null) { objectWriter = this.prettyPrintObjectMapper.writer(filters); } else { objectWriter = this.prettyPrintObjectMapper.writer(); } if (javaType != null && javaType.isContainerType()) { objectWriter = objectWriter.forType(javaType); } objectWriter.writeValue(generator, value); writeSuffix(generator, realObject); generator.flush(); } catch (JsonProcessingException ex) { throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex); } } else { clazz, MediaType mediaType) { // this should be mandatory overridden, // otherwise writeInternal() won't be called with custom PrettyFormattedBody type return (PrettyFormattedBody.class.equals(clazz) && canWrite(mediaType)) || super.canWrite(clazz, mediaType); } public static final class PrettyFormattedBody { private final Object body; private final boolean pretty; public PrettyFormattedBody(Object body, boolean pretty) { this.body = body; this.pretty = pretty; } public Object getBody() { return body; } public boolean isPretty() { return pretty; } } }
HealthController.java (a rather optional request parameter):
@RequestMapping(value = {"/", "/health"}, produces = APPLICATION_JSON_VALUE) public ResponseEntity<?> health(@RequestParam Optional<String> pretty) { return new ResponseEntity<>( new CustomJsonResponseMapper.PrettyFormattedBody(healthResult(), pretty.isPresent()), HttpStatus.OK); }
Example response http://localhost:8080 :
{"status":"OK","statusCode":200,"endpoints":["/aaa","/bbb","/ccc"]}
Response example http://localhost:8080?pretty :
{ "status": "OK", "statusCode": 200, "endpoints": [ "/aaa", "/bbb", "/ccc" ] }