45

After I upgraded to the newly released 2.2.0.RELEASE version of Spring Boot some of my tests failed. It appears that the MediaType.APPLICATION_JSON_UTF8 has been deprecated and is no longer returned as default content type from controller methods that do not specify the content type explicitly.

Test code like

String content = mockMvc.perform(get("/some-api")
            .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
            .andReturn()
            .getResponse()
            .getContentAsString();

suddenly did not work anymore as the content type was mismatched as shown below

java.lang.AssertionError: Content type 
Expected :application/json;charset=UTF-8
Actual   :application/json

Changing the code to .andExpect(content().contentType(MediaType.APPLICATION_JSON)) resolved the issue for now.

But now when comparing content to the expected serialized object there is still a mismatch if there are any special characters in the object. It appears that the .getContentAsString() method does not make use of the UTF-8 character encoding by default (any more).

java.lang.AssertionError: Response content expected:<[{"description":"Er hörte leise Schritte hinter sich."}]> but was:<[{"description":"Er hörte leise Schritte hinter sich."}]>
Expected :[{"description":"Er hörte leise Schritte hinter sich."}]
Actual   :[{"description":"Er hörte leise Schritte hinter sich."}]

How can I get content in UTF-8 encoding?

times29
  • 2,782
  • 2
  • 21
  • 40

10 Answers10

35

Yes. This is problem from 2.2.0 spring-boot. They set deprecation for default charset encoding.

.getContentAsString(StandardCharsets.UTF_8) - good but in any response would be populated ISO 8859-1 by default.

In my project I updated current created converter:

@Configuration
public class SpringConfig implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.stream()
            .filter(converter -> converter instanceof MappingJackson2HttpMessageConverter)
            .findFirst()
            .ifPresent(converter -> ((MappingJackson2HttpMessageConverter) converter).setDefaultCharset(UTF_8));
    }
...
robsiemb
  • 6,157
  • 7
  • 32
  • 46
  • This was the easiest solution recommended here! – times29 Dec 04 '19 at 10:54
  • 2
    This is a bit misleading as it just causes the content type header to be `application/json;charset=UTF-8` which defeats the purpose Spring deprecating the charset part to begin with. So wouldn't it be better to fix the test? But if you are going to use the deprecated value, I think the proper way is to use the ContentNegotiationConfigurer – rougou Oct 02 '20 at 16:05
24

Using .getContentAsString(StandardCharsets.UTF_8) instead of .getContentAsString() resolves the problem.

times29
  • 2,782
  • 2
  • 21
  • 40
  • This answer was available on Google since the 25th of April: https://github.com/spring-projects/spring-framework/issues/22788#issuecomment-486568650 – scre_www Dec 01 '19 at 18:42
  • 2
    This worked for me and was a very clean solution. `String json = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);` – Flavio Oliva Sep 23 '22 at 22:23
11

The default encoding character is no longer UTF-8 since the 5.2.0 version of spring.

To continue using UTF-8, you must set it in the ServletResponse of the MockMvc result. To set the default character encoding to UTF-8, do something like this in your setup method:

@Before
public void setUp() {
   mockMvc = webAppContextSetup(wac).addFilter(((request, response, chain) -> {
                response.setCharacterEncoding("UTF-8");
                chain.doFilter(request, response);
            })).build();
}

Then you can use the mockMvc instance to perform your request.

Hope this help.

black4bird
  • 617
  • 1
  • 5
  • 13
  • With this solution I would have to configure the mockMvc in every test class. This can be quite tedious for many test classes! – times29 Dec 04 '19 at 10:55
  • 1
    You could just create an abstract class and do it there, then extend it in your test classes. – rougou Oct 02 '20 at 16:31
4

Following on black4bird's answer, you can override character encoding for all your tests by placing a following MockMvcBuilderCustomizer implementation in your test Spring context:

@Component
class MockMvcCharacterEncodingCustomizer implements MockMvcBuilderCustomizer {
    @Override
    public void customize(ConfigurableMockMvcBuilder<?> builder) {
        builder.alwaysDo(result -> result.response.characterEncoding = "UTF-8");
    }
}

That might help if you don't want to explicitly setup MockMvc, and just use @AutoconfigureMockMvc.

2

I am using Spring Boot 1.5.15.RELEASE and faced the same issue when writing tests.

The first solution that helped me was to add .characterEncoding("UTF-8")) like this:

String content = mockMvc.perform(get("/some-api")
            .contentType(MediaType.APPLICATION_JSON)
            .characterEncoding("UTF-8"))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
            .andReturn()
            .getResponse()
            .getContentAsString();

I am using a StandaloneMockMvcBuilder in my test class, so the second solution that helped me was to create a filter e.g.:

private static class Utf8Filter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
        response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
        filterChain.doFilter(request, response);
    }
}

and later add it to the standaloneSetup method in my test class like this:

@Before
public void setup() {
    MockitoAnnotations.initMocks(this);
    final SomeResource someResource = new SomeResource(someService);
    this.restLankMockMvc = MockMvcBuilders.standaloneSetup(someResource)
        .setCustomArgumentResolvers(pageableArgumentResolver)
        .setControllerAdvice(exceptionTranslator)
        .setConversionService(createFormattingConversionService())
        .setMessageConverters(jacksonMessageConverter)
        .addFilter(new Utf8Filter())
        .build();
}
Zuljen
  • 21
  • 2
2

To restore the original behavior (Content-Type=application/json;charset=UTF-8) and allow your tests to pass as is, you could do the following:

@Configuration
public class MyWebConfig implements WebMvcConfigurer {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.defaultContentType(MediaType.APPLICATION_JSON_UTF8);
    }
...

However, since APPLICATION_JSON_UTF8 is deprecated and Spring's intention is not to include the charset, it might be better going forward to modify your tests. black4bird's solution worked for me.

rougou
  • 956
  • 9
  • 14
  • This should be the accepted answer. Does not create any additional filter and does not modify existing converter. Moreover the code is readable – Stanislas Klukowski May 21 '21 at 12:36
0

According to this pull request from the spring developers the UTF-8 header is not required anymore and therefore it's deprecated. If you're using the UTF-8 header in your application you might consider removing it from your application instead of trying fix your test. Just make sure you're using the Content-Type: application/json header and you should be fine.

scre_www
  • 2,536
  • 4
  • 20
  • 30
  • 1
    I think you don't understand the problem. I suggest you read the whole question and then reevaluate, if your answer delivers any value. My application is wokring perfectly fine, the problem is related to the tests. – times29 Nov 29 '19 at 15:43
  • I read the whole question again and reevaluated my answer, the answer is still the same. In your question you don't explain *why* the header is deprecated, I enriched your question with my post. I suggest you read the PR that I linked to, so you understand why the header is deprecated. If you understand the why, you might want to consider changing your test since your test is testing default behaviour in Spring 2.1.X, but it does not test the behaviour in Spring 2.2.X correctly. The Spring behaviour changed, your test should change accordingly if you accept the new Spring behaviour. – scre_www Dec 01 '19 at 18:37
  • You are not being very consistent here. In your answer you say "[...] instead of trying fix your test". In your comment you say "[...] your test should change accordingly if you accept the new Spring behaviour." – times29 Dec 04 '19 at 10:47
  • 2
    Every programmer faces deprecated values now and then. When something is deprecated, you could fix it somehow without researching *why* it got deprecated in the first place. This approach seems to be the way you're handling this problem. Now I suggest you look further and research *why* it got deprecated. If you understand that, you can make a better decision on what to do next. In your question there is nothing about the *why* you only tell us your test is failing due to a deprecated value which is poor research. I enriched the question with some research you didn't do AND upvoted the Q. – scre_www Dec 05 '19 at 11:27
  • @scre_www your answer only covers the part of the question about the assert on the returned _Content-Type_, which OP had already changed as indicated in the question. It does not explain why MockMVC is still reading the body using ISO instead of UTF-8 when calling `getContentAsString()` and how to solve that (looks like a bug TBH). – Didier L Jul 25 '22 at 22:06
0

Additional setting to MockMvc, .accept(MediaType.APPLICATION_JSON_UTF8_VALUE):

    String content = mockMvc.perform(get("/some-api")
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON_UTF8_VALUE))
        .andExpect(status().isOk())
        .andExpect(content().contentType(MediaType.APPLICATION_JSON))
        .andReturn()
        .getResponse()
        .getContentAsString();

This problem is not Spring Boot, but MockMvc specific one, I guess. So, a workaround must be applied to MockMvc only. (JSON must be encoded using UTF-8.)

related issue: Improper UTF-8 handling in MockMvc for JSON response · Issue #23622 · spring-projects/spring-framework

Community
  • 1
  • 1
0

if you are using maven, you can use the maven-surefire-plugin and enable UTF-8 encoding this way:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <argLine>-Dfile.encoding=UTF-8</argLine>
    </configuration>
</plugin>
Oleg
  • 1
  • 1
0

This works in Spring Boot 2.6.6

@Autowired
private WebApplicationContext wac;

private MockMvc mockMvc;

@PostConstruct
public void init() {
   mockMvc = MockMvcBuilders.webAppContextSetup(wac)
                            .defaultResponseCharacterEncoding(StandardCharsets.UTF_8)
                            .build();
}
Sergey Nemchinov
  • 1,348
  • 15
  • 21