Download/export problem (uniform return): No converter for xxx with preset Content-Type application/octet-stream; charset=UTF-8

1. Preface

The download interface returns download data normally. After the file stream is closed, the response returns success and the console reports an error

all content:
Probably means there is no converter. Contains the default content type “application/octet-stream;charset=UTF-8”

org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class com.cn.common.AjaxResult] with preset Content-Type 'application/octet-stream;charset=UTF-8'
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:319)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:194)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:78)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:135)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1080)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:973)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1010)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:902)


2. Source code

@GetMapping("/d1")
    public AjaxResult<?> d1(HttpServletResponse httpServletResponse){<!-- -->
        DownloadHuTool.download2_2(httpServletResponse);
        return AjaxResult.success();
    }
/**
     * HuTool HttpRequest method
     * Download the file and send it to the front end
     *
     * @param httpServletResponse httpServletResponse
     */
    public static void download2_2(HttpServletResponse httpServletResponse) {<!-- -->
        try {<!-- -->
            // file name
            String fileName = "AA.png";
            // Get the file
            HttpResponse execute = HttpRequest.get("https://emojimix.app/emojimixfusion/1_1.png").execute();
            //Set response header information to tell the front-end browser to download the file
            httpServletResponse.setContentType("application/octet-stream;charset=UTF-8");
            httpServletResponse.setCharacterEncoding("UTF-8");
            httpServletResponse.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));
            // httpServletResponse.setHeader("Content-Length", String.valueOf(count)); Pass out the file length so that the front end can implement the download progress bar
            // Get the output stream to write data
            OutputStream outputStream = httpServletResponse.getOutputStream();
            byte[] bytes = execute.bodyBytes();
            //Copy bytes to output stream
            outputStream.write(bytes);
            // Close the stream resource
            outputStream.close();
        } catch (IOException e) {<!-- -->
            throw new RuntimeException(e);
        }
    }
package com.cn.common;

/**
 * Unified return class
 *
 * @author Yph
 */
public class AjaxResult<T> {<!-- -->

    /**
     * status code
     */
    private Integer code;

    /**
     * Return content
     */
    private String msg;

    /**
     * data object
     */
    private T data;

    public AjaxResult(Integer code, String msg, T data) {<!-- -->
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public AjaxResult(Integer code, String msg) {<!-- -->
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {<!-- -->
        return code;
    }

    public void setCode(Integer code) {<!-- -->
        this.code = code;
    }

    public String getMsg() {<!-- -->
        return msg;
    }

    public void setMsg(String msg) {<!-- -->
        this.msg = msg;
    }

    public T getData() {<!-- -->
        return data;
    }

    public void setData(T data) {<!-- -->
        this.data = data;
    }

    @Override
    public String toString() {<!-- -->
        return "AjaxResult{" +
                "code=" + code +
                ", msg='" + msg + '\'' +
                ", data=" + data +
                '}';
    }

    public static <T> AjaxResult<T> success() {<!-- -->
        return new AjaxResult<>(HttpStatus.SUCCESS, "Operation successful", null);
    }

    public static <T> AjaxResult<T> success(T data) {<!-- -->
        return new AjaxResult<>(HttpStatus.SUCCESS, "Operation successful", data);
    }

    public static <T> AjaxResult<T> success(String msg, T data) {<!-- -->
        return new AjaxResult<>(HttpStatus.SUCCESS, msg, data);
    }

    public static <T> AjaxResult<T> success(Integer code, String msg, T data) {<!-- -->
        return new AjaxResult<>(code, msg, data);
    }

    public static <T> AjaxResult<T> error(T data) {<!-- -->
        return new AjaxResult<>(HttpStatus.ERROR, "Operation failed", data);
    }

    public static <T> AjaxResult<T> error(String msg, T data) {<!-- -->
        return new AjaxResult<>(HttpStatus.ERROR, msg, data);
    }

    public static <T> AjaxResult<T> error(String msg) {<!-- -->
        return new AjaxResult<>(HttpStatus.ERROR, msg, null);
    }

    public static <T> AjaxResult<T> error(T data, Integer code, String msg) {<!-- -->
        return new AjaxResult<>(code, msg, data);
    }
}

3. Solve the problem

Cause of the problem: Due to the download implementation class called by the download controller, the controller uses the unified return class AjaxResult object, but also sets the Content-Type to application/octet-stream;charset=UTF-8, which means that you are trying to return a binary File stream. In the Spring framework, when the returned object is not ResponseEntity or HttpEntity, Spring will try to find a suitable HttpMessageConverter to convert the returned object into a response body.

In your case, Spring tried to convert the AjaxResult object to the response body in application/octet-stream;charset=UTF-8 format, but no suitable converter was found, so an HttpMessageNotWritableException was thrown.

3.1. Method 1 (recommended)

This method still returns status information, highly recommended
In fact, the front end can only choose one of the two in response to JSON information and data flow, but it is very good to respond directly when there are exceptions.

 /**
     *Solution 1 (recommended)
     * Manually return status message
     */
    @GetMapping("/d1_1")
    public void d1_1(HttpServletResponse httpServletResponse) {<!-- -->
        try {<!-- -->
            DownloadHuTool.download2_2(httpServletResponse);
        } catch (Exception e) {<!-- -->
            e.printStackTrace();
            httpServletResponse.reset();
            AjaxResult<T> result = new AjaxResult<>(HttpStatus.ERROR, e.getMessage());
            String json = JSON.toJSONString(result);
            ServletUtils.renderString(httpServletResponse, json);
        }
    }
/**
     * Render the string to the client
     *
     * @param response rendering object
     * @param string string to be rendered
     */
    public static void renderString(HttpServletResponse response, String string) {<!-- -->
        try {<!-- -->
            response.setStatus(200);
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            response.getWriter().print(string);
        } catch (IOException e) {<!-- -->
            e.printStackTrace();
        }
    }

3.2. Method 2 (General)

Do not return information, return null directly. The disadvantage is that status information cannot be returned

 /**
     *Solution 2
     * If null is not returned, an exception will be reported
     */
    @GetMapping("/d1_1")
    public AjaxResult<?> d1_2(HttpServletResponse httpServletResponse) {<!-- -->
        DownloadHuTool.download2_2(httpServletResponse);
        return null;
    }

3.3. Method three (NO)

Put the logic for downloading the file into a separate method and don’t return any value. Then, call this method in the controller method and return an appropriate response. The controller method will return a ResponseEntity object that contains the file contents and appropriate response headers. Note that this method does not return an AjaxResult object, so the above exception will not be raised.

Transform the original code:

@GetMapping("/d1_2")
    public ResponseEntity<ByteArrayResource> d1_2() {<!-- -->
        // file name
        String fileName = "AA.png";
        // Get the file
        HttpResponse execute = HttpRequest.get("https://emojimix.app/emojimixfusion/1_1.png").execute();
        //Set response header information to tell the front-end browser to download the file
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        headers.set("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));
        // Get the output stream to write data
        byte[] bytes = execute.bodyBytes();
        ByteArrayResource resource = new ByteArrayResource(bytes);
        return ResponseEntity.ok().headers(headers).contentLength(bytes.length).body(resource);
    }