Announcement Announcement Module
Collapse
No announcement yet.
@ResponseBody and UTF-8 Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • @ResponseBody and UTF-8

    I have a method annotated with @ResponseBody and returning a string. The String contains Unicode characters, but when my browser renders my the response, all I see are ?? in place of the Unicode characters. I've also tried using a CharacterEncodingFilter and setting the encoding to UTF-8 and force encoding to true. This hasn't worked for the HTTP response either.

    Is there a way to let Spring know the returned content encoding is UTF-8? The only other way I see is not use Spring and write the content directly to the HttpServletResponse.

    Thanks.

  • #2
    Not sure this will solve your problem, but I've solved quite a lot of my character set problems using this in my web.xml:

    Code:
    <filter>
    	<filter-name>characterEncodingFilter</filter-name>
    	<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    	<init-param>
    		<param-name>encoding</param-name>
    		<param-value>UTF-8</param-value>
    	</init-param>
    	<init-param>
    		<param-name>forceEncoding</param-name>
    		<param-value>true</param-value>
    	</init-param>
    </filter>

    Comment


    • #3
      Make sure you declare the CharacterEncodingFilter as the first filter in web.xml and that you are using at least Servlet version 2.4 (check your web.xml version declaration) as forceEncoding won't work for earlier versions

      Comment


      • #4
        CharacterEncodingFilter can't help here, because String returned by @RequestBody-annotated method is encoded by StringHttpMessageConverter, which deduces the encoding from its specified content-type ("text/plain;charset=ISO-8859-1" by default). If you want to use @RequestBody, probably, you can reconfigure it something this way:
        Code:
        <bean class = "org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        	<property name = "messageConverters">
        		<list>
        			<bean class = "org.springframework.http.converter.StringHttpMessageConverter">
        				<property name = "supportedMediaTypes">
        					<list>
        						text/plain;charset=UTF-8
        					</list>
        				</property>
        			</bean>
        		</list>
        	</property>
        </bean>

        Comment


        • #5
          sometimes this codes can work, but sometimes not.
          Code:
          <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
           <property name = "messageConverters">
            <list>
             <bean class = "org.springframework.http.converter.StringHttpMessageConverter">
             <property name = "supportedMediaTypes">
              <list>
               <value>text/plain;charset=UTF-8</value>
              </list>
             </property>
             </bean>
            </list>
           </property>
          </bean>
          your will get error HTTP Error 406, when the request parameter Accept is '*/*', and you can't set the value with '*' ;

          So, I suggest that you configure like this
          Code:
          <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
           <property name = "messageConverters">
            <list>
             <bean class = "org.springframework.http.converter.StringHttpMessageConverter">
             <property name = "supportedMediaTypes">
              <list>
               <bean class="org.springframework.http.MediaType">
                <constructor-arg index="0" value="text"/>
                <constructor-arg index="1" value="plain"/>
                <constructor-arg index="2" value="UTF-8"/>
               </bean>
               <bean class="org.springframework.http.MediaType">
                <constructor-arg index="0" value="*"/>
                <constructor-arg index="1" value="*"/>
                <constructor-arg index="2" value="UTF-8"/>
               </bean>
              </list>
             </property>
             </bean>
            </list>
           </property>
          </bean>

          Comment


          • #6
            The proposed solution fails when the client doesn't specify the proper ACCEPT header, that would result in a 406. Also, the above solution does not work because */* is not allowed for MediaTypes (no matter how clever you are at constructing them).

            StringHttpMessageConverter should allow the DEFAULT_CHARSET to be set instead. This way you can configure it to return UTF-8 by default. All it would take is a simple setter: setDefaultCharset(String charset).

            That would solve the problem where the client's ACCEPT header is empty, and setting the MediaType is therefore not an option.


            UPDATE: Looking at the code I can see why a setter cannot be used. The value is needed in the constructor (super() call). Maybe an alternative would be to provide a constructor that would look like this:

            private static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");

            public ConfigurableStringHttpMessageConverter() {
            this(DEFAULT_CHARSET);
            }

            public ConfigurableStringHttpMessageConverter(Charset defaultCharset) {
            super(new MediaType("text", "plain", defaultCharset), MediaType.ALL);
            this.availableCharsets = new ArrayList<Charset>(Charset.availableCharsets().val ues());
            }
            The bean would only need:

            <bean id="stringHttpMessageConverter"
            class="org.springframework.http.converter.StringHt tpMessageConverter">
            <constructor-arg value="UTF-8"/>
            </bean>
            I have implemented my own ConfigurableStringHttpMessageConverter (essentially a copy of StringHttpMessageConverter) and it works fine.
            Last edited by springbee; Feb 23rd, 2010, 11:16 AM. Reason: added constructor code

            Comment


            • #7
              Originally posted by springbee View Post

              I have implemented my own ConfigurableStringHttpMessageConverter (essentially a copy of StringHttpMessageConverter) and it works fine.
              Man, thank you very much for snippets. But only one question: how to use ConfigurableStringHttpMessageConverter ? How to integrate it with my application?

              I made next definition in myapp-servlet.xml (like in your example as i see):

              Code:
              	<bean id="stringHttpMessageConverter" class="com.myapp.web.ConfigurableStringHttpMessageConverter">
              		<constructor-arg value="UTF-8"/>
              	</bean>
              but how now have it to work? it looks like it is doing nothing now...

              Comment


              • #8
                Just to be sure, here is the full code of ConfigurableStringHttpMessageConverter:

                Code:
                /*
                 * Copyright 2002-2009 the original author or authors.
                 *
                 * Licensed under the Apache License, Version 2.0 (the "License");
                 * you may not use this file except in compliance with the License.
                 * You may obtain a copy of the License at
                 *
                 *      http://www.apache.org/licenses/LICENSE-2.0
                 *
                 * Unless required by applicable law or agreed to in writing, software
                 * distributed under the License is distributed on an "AS IS" BASIS,
                 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                 * See the License for the specific language governing permissions and
                 * limitations under the License.
                 */
                
                package please.put.your.package.here.converters;
                
                import org.springframework.http.HttpInputMessage;
                import org.springframework.http.HttpOutputMessage;
                import org.springframework.http.MediaType;
                import org.springframework.http.converter.AbstractHttpMessageConverter;
                import org.springframework.util.FileCopyUtils;
                
                import java.io.IOException;
                import java.io.InputStreamReader;
                import java.io.OutputStreamWriter;
                import java.io.UnsupportedEncodingException;
                import java.nio.charset.Charset;
                import java.util.ArrayList;
                import java.util.List;
                
                /**
                 * Implementation of {@link org.springframework.http.converter.HttpMessageConverter} that can read and write strings.
                 *
                 * <p>By default, this converter supports all media types (<code>*/*</code>), and writes with a {@code
                 * Content-Type} of {@code text/plain}. This can be overridden by setting the {@link
                 * #setSupportedMediaTypes(java.util.List) supportedMediaTypes} property.</p>
                 * <p>Has a second constructor which can modify the defaultCharset.</p>
                 *
                 * @author Arjen Poutsma
                 * @author Matthijs Bierman
                 * @since 3.0
                 */
                public class ConfigurableStringHttpMessageConverter extends AbstractHttpMessageConverter<String> {
                
                	private final List<Charset> availableCharsets;
                    private static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");
                
                    public ConfigurableStringHttpMessageConverter() {
                        this(DEFAULT_CHARSET);
                    }
                
                    public ConfigurableStringHttpMessageConverter(Charset defaultCharset) {
                        super(new MediaType("text", "plain", defaultCharset), MediaType.ALL);
                        this.availableCharsets = new ArrayList<Charset>(Charset.availableCharsets().values());
                	}
                
                	@Override
                	public boolean supports(Class<? extends String> clazz) {
                		return String.class.equals(clazz);
                	}
                
                	@Override
                	public String readInternal(Class<String> clazz, HttpInputMessage inputMessage) throws IOException {
                		MediaType contentType = inputMessage.getHeaders().getContentType();
                		Charset charset = contentType.getCharSet() != null ? contentType.getCharSet() : DEFAULT_CHARSET;
                		return FileCopyUtils.copyToString(new InputStreamReader(inputMessage.getBody(), charset));
                	}
                
                	@Override
                	protected Long getContentLength(String s, MediaType contentType) {
                		if (contentType != null && contentType.getCharSet() != null) {
                			Charset charset = contentType.getCharSet();
                			try {
                				return (long) s.getBytes(charset.name()).length;
                			}
                			catch (UnsupportedEncodingException ex) {
                				// should not occur
                				throw new InternalError(ex.getMessage());
                			}
                		}
                		else {
                			return null;
                		}
                	}
                
                	@Override
                	protected void writeInternal(String s, HttpOutputMessage outputMessage) throws IOException {
                		outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());
                		MediaType contentType = outputMessage.getHeaders().getContentType();
                		Charset charset = contentType.getCharSet() != null ? contentType.getCharSet() : DEFAULT_CHARSET;
                		FileCopyUtils.copy(s, new OutputStreamWriter(outputMessage.getBody(), charset));
                	}
                
                	/**
                	 * Return the list of supported {@link Charset}.
                	 *
                	 * <p>By default, returns {@link Charset#availableCharsets()}. Can be overridden in subclasses.
                	 *
                	 * @return the list of accepted charsets
                	 */
                	protected List<Charset> getAcceptedCharsets() {
                		return this.availableCharsets;
                	}
                
                }
                Then you have to inject the bean into your AnnotationMethodHandlerAdapter if you want to use it with @ResponseBody:

                Code:
                <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
                        <property name="messageConverters">
                            <util:list>
                                <bean id="stringHttpMessageConverter"
                                      class="your.package.converters.ConfigurableStringHttpMessageConverter">
                                    <constructor-arg value="UTF-8"/>
                                </bean>
                            </util:list>
                        </property>
                    </bean>

                Comment


                • #9
                  It works! Thank you, springbee.

                  Comment


                  • #10
                    Same problem

                    I already test your code but i see the ??? characters insted ρρρ... i configure the bean in my xml but when i debug, i didn' see the readInternal and writeInternal Method execution... can you help me?

                    thanks.

                    Comment


                    • #11
                      Originally posted by carlossantillan View Post
                      I already test your code but i see the ??? characters insted ρρρ... i configure the bean in my xml but when i debug, i didn' see the readInternal and writeInternal Method execution... can you help me?

                      thanks.
                      hi.. described method works for Spring 3.0.1. It is exactly (i'm using spring 3.0.1). I tried it for Spring 3.0.3 and it did not work. The same results as for you. I did not find time to see what is the problem, so, i'm just using spring 3.0.1. Think need send bug report to guys. I could suggest you use spring 3.0.1 while they will fix it.

                      Comment


                      • #12
                        Following way is working for me. Read the accepted answer.

                        http://stackoverflow.com/questions/3...c-responsebody

                        Comment


                        • #13
                          Fix for issue: @ResponseBody and UTF-8

                          Code:
                              package com.your.package.spring.fix;
                          
                              import java.io.UnsupportedEncodingException;
                              import java.net.URLDecoder;
                              import java.net.URLEncoder;
                          
                              /**
                               * @author Szilard_Jakab (JaKi)
                               * Workaround for Spring 3 @ResponseBody issue - get incorrectly 
                                 encoded parameters from the URL (in example @ JSON response)
                               * Tested @ Spring 3.0.4
                               */
                              public class RepairWrongUrlParamEncoding {
                                  private static String restoredParamToOriginal;
                              
                                  /**
                                  * @param wrongUrlParam
                                  * @return Repaired url param (UTF-8 encoded)
                                  * @throws UnsupportedEncodingException
                                  */
                                  public static String repair(String wrongUrlParam) throws 
                                                                          UnsupportedEncodingException {
                                  /* First step: encode the incorrectly converted UTF-8 strings back to 
                                                     the original URL format
                                  */
                                  restoredParamToOriginal = URLEncoder.encode(wrongUrlParam, "ISO-8859-1");
                                  
                                  /* Second step: decode to UTF-8 again from the original one
                                  */
                                  return URLDecoder.decode(restoredParamToOriginal, "UTF-8");
                                  }
                              }
                          After I have tried lot of workaround for this issue.. I thought this out and it works fine.

                          Comment


                          • #14
                            or, if you want to use characters like "ľščťžύανι" simply add
                            Code:
                            <%@ page contentType="text/html; charset=UTF-8" %>
                            to your jsp page! this works with custom lopgin page too!

                            Comment


                            • #15
                              I didn't see this solution on the forum, so here is my 50 cents

                              you can add produces = "text/plain;charset=UTF-8" to the request mapping to force responesbody output encoding

                              @RequestMapping(value = "/rest/create/document", produces = "text/plain;charset=UTF-8")
                              @ResponseBody
                              public void create(Document document, HttpServletRespone respone) throws UnsupportedEncodingException {

                              Document newDocument = DocumentService.create(Document);

                              return jsonSerializer.serialize(newDocument);
                              }

                              Here is a blog explain this

                              Comment

                              Working...
                              X