Affects: spring-webmvc-5.1.2.RELEASE

1、There is a controller's return type is String in the controllers。 2、Add the @ControllerAdvice to change the response type of all controllers。 3、The Spring-framework will chose the StringHttpMessageConverter to convert the response body,and got an error。

//the error code:
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
                        (GenericHttpMessageConverter<?>) converter : null);
                if (genericConverter != null ?
                        ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
                        converter.canWrite(valueType, selectedMediaType)) {
           //使用了StringHttpMessageConverter
          //调用advice的相关方法,@ControllerAdvice 返回对象被修改。
                    body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
                            (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
                            inputMessage, outputMessage);
                    if (body != null) {
                        Object theBody = body;
                        LogFormatUtils.traceDebug(logger, traceOn ->
                                "Writing [" + LogFormatUtils.formatValue(theBody, traceOn) + "]");
                        addContentDispositionHeader(inputMessage, outputMessage);
                        if (genericConverter != null) {
                            genericConverter.write(body, targetType, selectedMediaType, outputMessage);
                        }
error stack:
java.lang.ClassCastException: classname cannot be cast to java.lang.String
        at org.springframework.http.converter.StringHttpMessageConverter.getContentLength(StringHttpMessageConverter.java:43) ~[spring-web-5.1.2.RELEASE.jar:5.1.2.RELEASE]
        at org.springframework.http.converter.AbstractHttpMessageConverter.addDefaultHeaders(AbstractHttpMessageConverter.java:260) ~[spring-web-5.1.2.RELEASE.jar:5.1.2.RELEASE]
        at org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:211) ~[spring-web-5.1.2.RELEASE.jar:5.1.2.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:292) ~[spring-webmvc-5.1.2.RELEASE.jar:5.1.2.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:180) ~[spring-webmvc-5.1.2.RELEASE.jar:5.1.2.RELEASE]

Comment From: bclozel

Instead of pointing to a code snippet in Framework, could you share a small sample application that shows the issue and explain how to reproduce it?

Comment From: jiangwh

👌。 1、The code sample of the controllerAdvice

@ControllerAdvice
@Slf4j
public class ReturnObjectAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter returnType,
            Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
            Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
            ServerHttpResponse response) {

        ObjectWrapper objectWrapper = returnType.getMethodAnnotation(ObjectWrapper.class);
        if (null != objectWrapper) {
            try {
                Object object = objectWrapper.value().newInstance();
                Method method = objectWrapper.value().getDeclaredMethod(objectWrapper.setData(), Object.class);
                if (null != method) {
                    method.setAccessible(true);
                    method.invoke(object, body);
                                         // change the response type 
                    return object;
                }
            } catch (Exception e) {
                log.error("", e);
            }
        }
        return body;
    }
}

2、The code of the controller

@RestController
@RequestMapping(value = "/a")
public class A {

    @GetMapping("/test")
    @ObjectWrapper
    public String checkCode() {
        return "some String";
    }
}

3、The annotation

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ObjectWrapper {
    Class<?> value() default ResponseWrapper.class;

    String setData() default "setData";

    String setCode() default "setCode";

    String setMessage() default "setMessage";
}

4、The response type

@Getter
@Setter
public class ResponseWrapper<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    private int code;
    private String message;

    private T data;

    public ResponseWrapper() {
        this.code = HttpStatus.OK.value();
        this.message = HttpStatus.OK.getReasonPhrase();
    }

    public ResponseWrapper(HttpStatus status) {
        this.code = status.value();
        this.message = status.getReasonPhrase();
    }

    public ResponseWrapper(T data) {

        if (isEmpty(data)) {
            this.code = HttpStatus.NO_CONTENT.value();
            this.message = HttpStatus.NO_CONTENT.getReasonPhrase();
        } else {
            this.code = HttpStatus.OK.value();
            this.message = HttpStatus.OK.getReasonPhrase();
            this.data = data;
        }
    }

    public ResponseWrapper(HttpStatus status, T data) {
        this.code = status.value();
        this.message = status.getReasonPhrase();
        this.data = data;
    }

    @SuppressWarnings("rawtypes")
    public boolean isEmpty(T data) {
        return data == null || data instanceof Collection && ((Collection) data).isEmpty();
    }

    @Override
    public String toString() {
        try {
            return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE);
        } catch (Exception e) {
            return super.toString();
        }
    }
}

Comment From: bclozel

This is the expected behavior of the ResponseBodyAdvice interface. As you can see in its other method supports, at that point the message converter has been already selected by looking at the return type of the method and the produces condition in the @GetMapping.

If you'd like to force one or more specific Content-Type for the response, you should express that in the @GetMapping annotation. In any case, your ReturnObjectAdvice should refine its supports implementation to only return true if the selected HttpMessageConverter can serialize your wrapper object.

For further questions, please use StackOverflow. Thanks!

Comment From: jiangwh

Thank you!

Comment From: swithun-liu

👌。 1、The code sample of the controller device

```java @ControllerAdvice @Slf4j public class ReturnObjectAdvice implements ResponseBodyAdvice {

@Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return true; }

@Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

  ObjectWrapper objectWrapper = returnType.getMethodAnnotation(ObjectWrapper.class);
  if (null != objectWrapper) {
      try {
          Object object = objectWrapper.value().newInstance();
          Method method = objectWrapper.value().getDeclaredMethod(objectWrapper.setData(), Object.class);
          if (null != method) {
              method.setAccessible(true);
              method.invoke(object, body);
                                     // change the response type 
              return object;
          }
      } catch (Exception e) {
          log.error("", e);
      }
  }
  return body;

} } ```

2、The code of the controller

```java @RestController @RequestMapping(value = "/a") public class A {

@GetMapping("/test") @ObjectWrapper public String checkCode() { return "some String"; } } ```

3、The annotation

```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ObjectWrapper { Class<?> value() default ResponseWrapper.class;

String setData() default "setData";

String setCode() default "setCode";

String setMessage() default "setMessage"; } ```

4、The response type

```java @Getter @Setter public class ResponseWrapper implements Serializable { private static final long serialVersionUID = 1L; private int code; private String message;

private T data;

public ResponseWrapper() { this.code = HttpStatus.OK.value(); this.message = HttpStatus.OK.getReasonPhrase(); }

public ResponseWrapper(HttpStatus status) { this.code = status.value(); this.message = status.getReasonPhrase(); }

public ResponseWrapper(T data) {

  if (isEmpty(data)) {
      this.code = HttpStatus.NO_CONTENT.value();
      this.message = HttpStatus.NO_CONTENT.getReasonPhrase();
  } else {
      this.code = HttpStatus.OK.value();
      this.message = HttpStatus.OK.getReasonPhrase();
      this.data = data;
  }

}

public ResponseWrapper(HttpStatus status, T data) { this.code = status.value(); this.message = status.getReasonPhrase(); this.data = data; }

@SuppressWarnings("rawtypes") public boolean isEmpty(T data) { return data == null || data instanceof Collection && ((Collection) data).isEmpty(); }

@Override public String toString() { try { return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE); } catch (Exception e) { return super.toString(); } } } ```

Can I find samples like this in spring documentation?

Comment From: cbsingh1

Any recommendation for how to select correct MessageConverter when applying ResponseBodyAdvice for String return type ?

.