javajsonspringspring-boot

RestClient POST response is encoded/garbled (Spring Boot 3.5)


I am trying to submit a POST request to an API to get a JSON payload in Response. My application is a Spring Boot 3.5 application. I am using RestClient to make the request. The request is accepted successfully, but the response I get back in the response body is unprintable / encoded characters. Status Code is 200. I am able to make successful request in Postman

Here is my constructor code which sets up the RestClient object:

public VivxWebApiService(RestClient.Builder restClientBuilder, ApplicationLocationConfiguration applicationLocationConfiguration) {
        List<HttpMessageConverter<?>> vivxHttpMessageConverters = new ArrayList<>();
        
        vivxHttpMessageConverters.add(new FormHttpMessageConverter());
        vivxHttpMessageConverters.add(new MappingJackson2HttpMessageConverter());

        this.restClient = restClientBuilder
                .baseUrl(applicationLocationConfiguration.getVivxWebApi())
                .messageConverters(vivxHttpMessageConverters)
                .build();
        this.restClient.head().accept(MediaType.APPLICATION_JSON);
    }

The method that does the actual POST call:

public void getDesignColors(String designName) {
        LOG.debug("Attempting to get design colors for design: {}", designName);
        String getColorsUri = "/design/colors";
        
        MultiValueMap<String, String> mvMap = new LinkedMultiValueMap<>();
        mvMap.add("name",designName);
        
        ResponseEntity<VivxDesignColorDataResponse> getColorsResponse = restClient.post()
                .uri(getColorsUri)
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(mvMap)
                .retrieve()
                .toEntity(VivxDesignColorDataResponse.class);
        LOG.debug("GetColorResponse: {}", getColorsResponse);
    }

I have tried many different iterations of using ".toEntity()" or ".body()" to get the response, but nothing seems to work.

I did put a request interceptor in the RestClient to get debug, and this is what it shows:

2025-07-23T21:23:03.758-05:00 DEBUG 3000 --- [art-services] [nio-8083-exec-1] c.j.b.a.service.VivxWebApiService        : Attempting to get design colors for design: 3408315_SP032826_71153
2025-07-23T21:23:03.762-05:00 DEBUG 3000 --- [art-services] [nio-8083-exec-1] o.s.web.client.DefaultRestClient         : Writing [{name=[3408315_SP032826_71153]}] as "application/x-www-form-urlencoded" with org.springframework.http.converter.FormHttpMessageConverter
2025-07-23T21:23:08.478-05:00  INFO 3000 --- [art-services] [nio-8083-exec-1] c.j.b.a.service.VivxWebApiService        : Request: POST https://devneffvivx.jostensaws.com/vivxwebapi/design/colors
2025-07-23T21:23:08.478-05:00  INFO 3000 --- [art-services] [nio-8083-exec-1] c.j.b.a.service.VivxWebApiService        : Response headers: [Content-Type:"application/x-www-form-urlencoded", Content-Length:"27"]
2025-07-23T21:23:08.478-05:00  INFO 3000 --- [art-services] [nio-8083-exec-1] c.j.b.a.service.VivxWebApiService        : Request body: name=3408315_SP032826_71153
2025-07-23T21:23:10.645-05:00  INFO 3000 --- [art-services] [nio-8083-exec-1] c.j.b.a.service.VivxWebApiService        : Response status: 200 OK
2025-07-23T21:23:10.645-05:00  INFO 3000 --- [art-services] [nio-8083-exec-1] c.j.b.a.service.VivxWebApiService        : Response headers: [:status:"200", access-control-allow-headers:"Content-Type", access-control-allow-origin:"*", content-encoding:"gzip", content-type:"application/json", date:"Thu, 24 Jul 2025 02:23:08 GMT", server:"Microsoft-HTTPAPI/2.0"]
2025-07-23T21:23:14.496-05:00  INFO 3000 --- [art-services] [nio-8083-exec-1] c.j.b.a.service.VivxWebApiService        : Response body: �
�C�!�=���9�iHq��h껬&
2025-07-23T21:23:14.498-05:00 DEBUG 3000 --- [art-services] [nio-8083-exec-1] c.j.b.a.service.VivxWebApiService        : GetColorResponse: <200 OK OK,[:status:"200", access-control-allow-headers:"Content-Type", access-control-allow-origin:"*", content-encoding:"gzip", content-type:"application/json", date:"Thu, 24 Jul 2025 02:23:08 GMT", server:"Microsoft-HTTPAPI/2.0"]>

Any idea what setup I may have incorrect/missing?


Solution

  • 2025-07-23T21:23:10.645-05:00  INFO 3000 --- [art-services] [nio-8083-exec-1] c.j.b.a.service.VivxWebApiService        : Response headers: [:status:"200", access-control-allow-headers:"Content-Type", access-control-allow-origin:"*", content-encoding:"gzip", content-type:"application/json", date:"Thu, 24 Jul 2025 02:23:08 GMT", server:"Microsoft-HTTPAPI/2.0"]
    

    This line says it all. To be more precise this header content-encoding:"gzip", gives it away, you are getting a gzipped response. Which you must first decompress before you can use it.

    You can do this in 2 ways

    1. Use a ClientInterceptor to wrap the incoming response to decompress
    2. Use a different HTTP client which does the decompression.

    Response Interceptor

    First create a custom response

    public class GzipDecompressingClientHttpResponse implements ClientHttpResponse {
    
        private final ClientHttpResponse response;
    
        public GzipDecompressingClientHttpResponse(ClientHttpResponse response) {
            this.response = response;
        }
    
        @Override
        public InputStream getBody() throws IOException {
            InputStream body = response.getBody();
            if (isGzipped(response)) {
                return new GZIPInputStream(new BufferedInputStream(body));
            }
            return body;
        }
    
        private boolean isGzipped(ClientHttpResponse response) {
            String contentEncoding = response.getHeaders().getFirst("Content-Encoding");
            return contentEncoding != null && contentEncoding.toLowerCase().contains("gzip");
        }
    
    
        @Override
        public HttpStatusCode getStatusCode() throws IOException {
            return response.getStatusCode();
        }
    
        @Override
        public String getStatusText() throws IOException {
            return response.getStatusText();
        }
    
        @Override
        public void close() {
            response.close();
        }
    
        @Override
        public org.springframework.http.HttpHeaders getHeaders() {
            return response.getHeaders();
        }
    }
    

    Then add an interceptor to wrap the incoming ClientResponse with this GzipDecompressingClientHttpResponse.

    public class GzipResponseInterceptor implements ClientHttpRequestInterceptor {
    
      public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
                throws IOException {
        return new GzipDecompressingClientHttpResponse(execution.execute(request, body));
      }
    }
    
    this.restClient = restClientBuilder
        .baseUrl(applicationLocationConfiguration.getVivxWebApi())
        .messageConverters(vivxHttpMessageConverters)
        .requestInterceptor(new GzipResponseInterceptor());
        .build();
    

    Benefit of this solution is that it will work regardless of the technology in use.

    Use Apache Http Client

    You can also switch the HTTP client used by the RestClient when using the default it will use the JDK provided HTTP client. You can switch this to one that automatically will support gzip responses, like Apache Http Client.

    You will need to add a dependency on the client and configure the builder to take that as the HTTP client to use.

    this.restClient = restClientBuilder
        .baseUrl(applicationLocationConfiguration.getVivxWebApi())
        .messageConverters(vivxHttpMessageConverters)
        .requestFactory(new HttpComponentsClientHttpRequestFactory(HttpClientBuilder.create().build())););
        .build();
    

    This will work without adding an additional interceptor, drawback is you need an additional dependency.

    See also this answer and this blog which were used as inspiration for this answer.