You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot升级后application/octet-stream不支持问题排查

问题描述

将Spring Boot从2.7.4版本升级到3.1.5后,出现org.springframework.web.HttpMediaTypeNotSupportedException: Content-Type 'application/octet-stream' is not supported错误,升级前所有功能运行正常。技术栈为Angular前端 + OpenAPI Generator 7.0.0生成前后端代码。

OpenAPI YAML配置

/testdimension/file-upload:
  post:
    tags:
      - TestDimension
    operationId: addFilesToTestExecution
    summary: Send files and IDs to the backend
    requestBody:
      required: true
      content:
        multipart/form-data:
          schema:
            type: object
            properties:
              toolId:
                type: integer
                format: int64
              ids:
                type: array
                items:
                  type: integer
                  format: int64
                description: test dimensions array
              files:
                type: array
                items:
                  type: string
                  format: binary
                description: test files to upload
            required:
              - ids
              - files
    responses:
      '200':
        description: OK
      '400':
        description: Invalid request payload
      '500':
        description: Internal server error

生成的Spring接口类代码

@Operation(
    operationId = "addFilesToTestExecution",
    summary = "Send files and IDs to the backend",
    tags = { "TestDimension" },
    responses = {
        @ApiResponse(responseCode = "200", description = "OK"),
        @ApiResponse(responseCode = "400", description = "Invalid request payload"),
        @ApiResponse(responseCode = "500", description = "Internal server error")
    }
)
@RequestMapping(
    method = RequestMethod.POST,
    value = "/testdimension/file-upload",
    consumes = "multipart/form-data"
)
default ResponseEntity<Void> addFilesToTestExecution(
    @Parameter(name = "ids", description = "test dimensions array", required = true) @RequestPart(value = "ids", required = true) List<Long> ids,
    @Parameter(name = "files", description = "test files to upload", required = true) @RequestPart(value = "files", required = true) List<MultipartFile> files,
    @Parameter(name = "toolId", description = "") @RequestParam(value = "toolId", required = false) Long toolId
) {
    return getDelegate().addFilesToTestExecution(ids, files, toolId);
}

生成的Angular请求代码

/**
 * Send files and IDs to the backend
 * @param ids test dimensions array
 * @param files test files to upload
 * @param toolId 
 * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
 * @param reportProgress flag to report request and response progress.
 */
public addFilesToTestExecution(ids: Array<number>, files: Array<Blob>, toolId?: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined}): Observable<any>;
public addFilesToTestExecution(ids: Array<number>, files: Array<Blob>, toolId?: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined}): Observable<HttpResponse<any>>;
public addFilesToTestExecution(ids: Array<number>, files: Array<Blob>, toolId?: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined}): Observable<HttpEvent<any>>;
public addFilesToTestExecution(ids: Array<number>, files: Array<Blob>, toolId?: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: undefined}): Observable<any> {
    if (ids === null || ids === undefined) {
        throw new Error('Required parameter ids was null or undefined when calling addFilesToTestExecution.');
    }
    if (files === null || files === undefined) {
        throw new Error('Required parameter files was null or undefined when calling addFilesToTestExecution.');
    }

    let headers = this.defaultHeaders;

    let httpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
    if (httpHeaderAcceptSelected === undefined) {
        // to determine the Accept header
        const httpHeaderAccepts: string[] = [
        ];
        httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
    }
    if (httpHeaderAcceptSelected !== undefined) {
        headers = headers.set('Accept', httpHeaderAcceptSelected);
    }

    // to determine the Content-Type header
    const consumes: string[] = [
        'multipart/form-data'
    ];

    const canConsumeForm = this.canConsumeForm(consumes);

    let formParams: { append(param: string, value: any): any; };
    let useForm = false;
    let convertFormParamsToString = false;
    // use FormData to transmit files using content-type "multipart/form-data"
    // see https://stackoverflow.com/questions/4007969/application-x-www-form-urlencoded-or-multipart-form-data
    useForm = canConsumeForm;
    if (useForm) {
        formParams = new FormData();
    } else {
        formParams = new HttpParams({encoder: this.encoder});
    }

    if (toolId !== undefined) {
        formParams = formParams.append('toolId', <any>toolId) as any || formParams;
    }
    if (ids) {
        if (useForm) {
            ids.forEach((element) => {
                formParams = formParams.append('ids', <any>element) as any || formParams;
        })
        } else {
            formParams = formParams.append('ids', ids.join(COLLECTION_FORMATS['csv'])) as any || formParams;
        }
    }
    if (files) {
        if (useForm) {
            files.forEach((element) => {
                formParams = formParams.append('files', <any>element) as any || formParams;
        })
        } else {
            formParams = formParams.append('files', files.join(COLLECTION_FORMATS['csv'])) as any || formParams;
        }
    }

    let responseType: 'text' | 'json' = 'json';
    if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) {
        responseType = 'text';
    }

    return this.httpClient.post<any>(`${this.configuration.basePath}/testdimension/file-upload`,
        convertFormParamsToString ? formParams.toString() : formParams,
        {
            responseType: <any>responseType,
            withCredentials: this.configuration.withCredentials,
            headers: headers,
            observe: observe,
            reportProgress: reportProgress
        }
    );
}

已尝试修改YAML中的Content-Type配置、在Spring配置中启用octet-stream,均未解决问题,询问错误来自前端还是后端。


错误来源分析与解决方案

这个错误来自后端,核心原因是Spring Boot 3.x(基于Spring Framework 6.x)对Multipart请求的校验逻辑大幅收紧,结合你的代码细节,具体原因和解决方法如下:

核心原因

  1. Spring接口用@RequestPart接收ids数组,但Angular生成的代码是逐个向FormData中appendids参数,导致该部分的Content-Type默认是application/octet-stream;
  2. Spring Boot 2.7.x对这种非预期媒体类型的兼容性更强,会自动转换,但3.x不再支持这种自动兼容,直接抛出媒体类型不支持的异常。

解决方法

方法1:修改OpenAPI YAML,规范数组参数的媒体类型

在ids字段定义中添加contentType,明确指定为application/json,让生成的代码以JSON格式传递数组:

ids:
  type: array
  items:
    type: integer
    format: int64
  description: test dimensions array
  contentType: application/json # 新增该行

重新生成前后端代码后,Angular会将整个ids数组作为JSON字符串传入,Spring可正确识别媒体类型并解析为List<Long>。

方法2:修改Spring接口,兼容现有前端请求

如果不想修改前端代码,可将@RequestPart改为@RequestParam接收数组:

@Parameter(name = "ids", description = "test dimensions array", required = true) 
@RequestParam(value = "ids", required = true) List<Long> ids,

@RequestParam会自动解析FormData中多个同名的ids参数为数组,无需关心单个参数的Content-Type。

方法3:全局配置Spring放宽媒体类型校验

在Spring Boot配置类中添加消息转换器,允许application/octet-stream转换为目标类型:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        converter.setSupportedMediaTypes(Arrays.asList(
                MediaType.APPLICATION_JSON,
                MediaType.APPLICATION_OCTET_STREAM
        ));
        converters.add(converter);
    }
}

此方法为全局兼容方案,适合不想修改业务代码的场景,但会降低接口的校验严格性。

内容的提问来源于stack exchange,提问作者Maks

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 10:43:13