I'm using Spring boot 3.4.4 calling a remote xml service where the response is in ISO-8859-1 encoding.
Response from server ->
HTTP/1.1 200 OK
Content-type: application/xml;charset=iso-8859-1
Date: Fri, 11 Apr 2025 06:51:09 +0200
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'; sandbox; default-src 'none'
Content-length: 1163
<?xml version="1.0"?>
<programs>
<ES_PRODUCT>
<p_product_type>Episode</p_product_type>
<p_product_productcode>123</p_product_productcode>
<p_product_originaltitle>Bue, pil, økser og stædighed</p_product_originaltitle>
<prd_external_reference>4714857215527</prd_external_reference>
<image1Sequence>0</image1Sequence>
<image1Type>Standard</image1Type>
<image1URN>https://foo.bar</image1URN>
</ES_PRODUCT>
</programs>
The restclient is configured to accept ISO-8859-1 encoding ->
@Bean(name = "woRestClient")
RestClient woRestClient(RestClient.Builder builder) {
var factory = new JdkClientHttpRequestFactory(createHttpClient());
return builder
.baseUrl(configuration.endpointUrl)
.messageConverters(httpMessageConverters -> {
httpMessageConverters.forEach(httpMessageConverter -> {
if (httpMessageConverter instanceof Jaxb2RootElementHttpMessageConverter converter) {
converter.setDefaultCharset(StandardCharsets.ISO_8859_1);
}
});
})
.requestFactory(factory)
.build();
}
private HttpClient createHttpClient() {
return HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.connectTimeout(Duration.ofSeconds(5))
.build();
}
The service ->
@Service
public class TheService {
private final RestClient restClient;
public TheService(RestClient restClient) {
this.restClient = restClient;
}
public Programs getStuff() {
var programs = restClient.get()
.uri("/someuri")
.retrieve()
.body(Programs.class);
return programs;
}
}
And the pom ->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.woodstox</groupId>
<artifactId>woodstox-core</artifactId>
<version>6.5.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-xjc-plugin</artifactId>
<version>4.0.0</version>
<executions>
<execution>
<id>xjc</id>
<phase>generate-sources</phase>
<goals>
<goal>xsdtojava</goal>
</goals>
</execution>
</executions>
<configuration>
<xsdOptions>
<xsdOption>
<xsd>${basedir}/src/main/spec/myschema.xsd</xsd>
<bindingFile>${basedir}/src/main/spec/binding.xjb</bindingFile>
<extension>true</extension>
</xsdOption>
</xsdOptions>
</configuration>
</plugin>
</plugins>
</build>
</project>
When calling the service the response still seems to be treated as a utf-8 stream since I get an error of Invalid byte 1 of 1-byte UTF-8 sequence.] ->
org.springframework.web.client.RestClientException: Error while extracting response for type [generated.Programs] and content type [application/xml;charset=iso-8859-1]
at org.springframework.web.client.DefaultRestClient.readWithMessageConverters(DefaultRestClient.java:261)
at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.readBody(DefaultRestClient.java:814)
at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.lambda$body$0(DefaultRestClient.java:745)
at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.exchangeInternal(DefaultRestClient.java:574)
at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.exchange(DefaultRestClient.java:535)
at org.springframework.web.client.RestClient$RequestHeadersSpec.exchange(RestClient.java:677)
at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.executeAndExtract(DefaultRestClient.java:809)
at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.body(DefaultRestClient.java:745)
at com.example.demo.whatson.WhatsonService.getProduction(WhatsonService.java:24)
at com.example.demo.whatson.WhatsonServiceTest.testGetProduction(WhatsonServiceTest.java:18)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: org.springframework.http.converter.HttpMessageNotReadableException: Could not unmarshal to [class generated.Programs]: jakarta.xml.bind.UnmarshalException
- with linked exception:
[com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException: Invalid byte 1 of 1-byte UTF-8 sequence.]
at org.springframework.http.converter.xml.AbstractXmlHttpMessageConverter.readInternal(AbstractXmlHttpMessageConverter.java:78)
at org.springframework.http.converter.AbstractHttpMessageConverter.read(AbstractHttpMessageConverter.java:198)
at org.springframework.web.client.DefaultRestClient.readWithMessageConverters(DefaultRestClient.java:244)
... 12 more
Caused by: jakarta.xml.bind.UnmarshalException
- with linked exception:
[com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException: Invalid byte 1 of 1-byte UTF-8 sequence.]
at jakarta.xml.bind.helpers.AbstractUnmarshallerImpl.createUnmarshalException(AbstractUnmarshallerImpl.java:294)
at org.glassfish.jaxb.runtime.v2.runtime.unmarshaller.UnmarshallerImpl.createUnmarshalException(UnmarshallerImpl.java:539)
at org.glassfish.jaxb.runtime.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:224)
at org.glassfish.jaxb.runtime.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:189)
at jakarta.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:134)
at jakarta.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:117)
at org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter.readFromSource(Jaxb2RootElementHttpMessageConverter.java:141)
at org.springframework.http.converter.xml.AbstractXmlHttpMessageConverter.readInternal(AbstractXmlHttpMessageConverter.java:72)
... 14 more
Caused by: com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException: Invalid byte 1 of 1-byte UTF-8 sequence.
at java.xml/com.sun.org.apache.xerces.internal.impl.io.UTF8Reader.invalidByte(UTF8Reader.java:702)
at java.xml/com.sun.org.apache.xerces.internal.impl.io.UTF8Reader.read(UTF8Reader.java:568)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLEntityScanner.load(XMLEntityScanner.java:1699)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLEntityScanner.skipChar(XMLEntityScanner.java:1375)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2762)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:605)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:114)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:542)
at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:889)
at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:825)
at java.xml/com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141)
at java.xml/com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1224)
at java.xml/com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:637)
at org.glassfish.jaxb.runtime.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:218)
... 19 more
Any help to set up spring-boot to accept iso encoding would be appreciated.
I also made a try : https://framagit.org/FBibonne/poc-java/-/tree/iso8859?ref_type=heads : this litle project tries to reproduce your context with the class ConfigRestClient which provides RestClients to retrieve Product entites (simplified version of your )
Then I made tests mocking a server which serves xml encoding with ISO-8859-1 : https://framagit.org/FBibonne/poc-java/-/blob/iso8859/src/test/java/poc/java/springrestclient/ConfigRestClientTest.java?ref_type=heads : each test configures a mock server to return xml, gets a ResClient from ConfigRestClient and calls retrieve with the RestClient as your method getStuff()
does.
The different tests try to explain the problem and suggest a fix :
faillingCallWithIso8859()
: reproduces the initial problem (checking the exception raised is about xml encodingtestOKWithUTF8()
checks it works if the server responses using UTF-8testOKWithCorrectHeaderXML()
tries a fix with the server side adding encoding in xml header : I think it is the ideal solutiontestOkWithClientFixingEncoding()
tries an other RestClient (provided by poc.java.springrestclient.ConfigRestClient#woEncodingFixingRestClient
) which intercept the response made by the spring ClientHttpRequest
and decode the body to re-encode it in UTF-8. this is a poor workaround because it requires to rewrite some spring code (class poc.java.springrestclient.ConfigRestClient.BodyReEncodedResponse which rewrites body like org.springframework.http.client.BufferingClientHttpResponseWrapper). But it works.If you introduced such a RestClient in your application, you would only use it for the problematic endpoint thanks to @Qualifier adn/or @Primary.
I don't think the problem resides in Spring RestClient : in fact, it seems impossible to set up externally the encoding used by Jaxb unmarshalling : see Override declared encoding during unmarshalling with JAXB : the suggested solution is to process byte decoding outside jaxb to control encoding. I neither found in Jaxb a way to do it : jakarta.xml.bind.helpers.AbstractUnmarshallerImpl#setProperty does not support any property set (raise exceptions)