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

Spring Boot接口测试遇Error 415不支持媒体类型问题求助

415 Unsupported Media Type 错误分析与解决

问题场景

使用Spring Boot开发Web服务时,测试一个同时接收DTO列表与文件的接口,出现415 "Unsupported Media Type"错误。已尝试用@RequestPart注解接收DTO列表,并通过Postman以form-data形式发送数据,但报错依旧;若将DTO列表改为@RequestBody以纯JSON发送,列表可正常接收,但文件部分仍报错。

错误信息

Content-Type 'application/octet-stream' is not supported.
"status": 415,
"error": "Unsupported Media Type"

控制器代码

@PostMapping("/upload")
public ResponseEntity<?> handleFileUpload(
                            @RequestPart(required = false) List<AtributoDto> atributoDtoList,
                            @RequestPart( value = "file", required = false) MultipartFile file,
                     HttpSession session,
                    @RequestPart(value = "entityId", required = false) Long entityId) {
    log.info("Inicio de handleFileUpload con file: {}", file);
    ResponseEntity<?> response;
    try {
        response = ResponseEntity.ok((atributoService.processAtributos(atributoDtoList,file,session,entityId)));
        log.info("El proceso de carga de archivos se completó con éxito");
    }catch (IOException e){
        log.error("Error en el proceso de carga de archivos: {}", e.getMessage());
        response = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error en el proceso de carga de archivos");
    }
    return response;
}

Service方法代码

@Transactional
public String processAtributos(List<AtributoDto> atributoDtoList, MultipartFile file, HttpSession session, Long entityId) throws IOException {
    log.info("Inicio del método processAtributos");
    List<Atributo> ats = new ArrayList<>();
    try {

        if (atributoDtoList != null) {
            log.info("Procesando lista de atributos y archivo");

            StringBuilder listaAtributos = new StringBuilder(" ");
            String consultaid = null;
            for (int i = 0; i < atributoDtoList.size(); i++) {
                AtributoDto dto = atributoDtoList.get(i);
                Atributo atributo = new Atributo();
                atributo.setNombreAtributo(dto.nombreAtributo());
                atributo.setDescripcion(dto.descripcion());
                atributo.setXxcntIdTipoDatoFk(dto.xxcntIdTipoDatoFk());
                atributo.setLongitud(dto.longitud());
                atributo.setEsAuditable(dto.esAuditable());
                atributo.setMascara(dto.mascara());
                atributo.setDueno(dto.dueno());
                atributo.setProcesoNace(dto.procesoNace());
                atributo.setEsRequeridoEnFuente(dto.esRequeridoEnFuente());
                atributo.setXxcntIdConsultaFk(dto.xxcntIdConsultaFk());
                atributo.setNombreConsulta(dto.nombreConsulta());
                atributo.setIncluirModificar(dto.modificadoPor());
                if (i == 0) {
                    atributo.setEsLlavePrimaria('1');
                    listaAtributos = new StringBuilder("'" + atributo.getNombreConsulta() + "'");
                    consultaid = String.valueOf(atributo.getXxcntIdConsultaFk());
                } else {
                    atributo.setEsLlavePrimaria('0');
                    listaAtributos.append(",'").append(atributo.getNombreConsulta()).append("'");
                }
                atributo.setEstatus('1');

                /**
                 * Si el objeto JSON tiene una propiedad "found" que es igual a "1", el código interpreta esto como una señal de que el atributo ya existe y necesita ser modificado.
                 * En este caso, busca el atributo existente en la base de datos usando su ID, actualiza sus propiedades, y luego guarda los cambios en la base de datos.
                 * */
                if ("1".equals(dto.found())) { // si el atributo ya existe
                    atributo.setUsuarioModificacion("UsuarioCNT");
                    Long idmodificar = dto.xxcntIdAtributoPk();
                    atributo.setXxcntIdAtributoPk(idmodificar);
                    Date date = new Date();
                    atributo.setFechaModificacion(date);
                    Atributo atrimodificar = getAtributoById(idmodificar);
                    atributo.setFechaCreacion(atrimodificar.getFechaCreacion());
                    atributo.setUsuarioCreacion(atrimodificar.getUsuarioCreacion());
                } else {
                    atributo.setUsuarioCreacion("UsuarioCNT");
                    Date date = new Date();
                    atributo.setFechaCreacion(date);
                }
                Atributo at = createNewAtributte(atributo);
                ats.add(at);
            }
            updateDeleteAtributos(consultaid, listaAtributos.toString());
            consultaDominioService.activarConsulta(consultaid);
            log.info("Lista de atributos procesada");
        } else {
            log.info("No se recibió lista de atributos");
        }


        if (file != null) {
            log.info("Archivo recibido, iniciando procesamiento");
            FileContentHolderDTO fileContentHolderDTO = getFileContentHolderDTO(file);
            List<String> fRow = fileContentHolderDTO.getFRow();
            // escribir el archivo en el disco
            byte[] bytes = file.getBytes();
            Path path = Paths.get(fileContentHolderDTO.getDestination() + file.getOriginalFilename()); // concatena el nombre del archivo con la ruta
            Files.write(path, bytes);

            Entidad e = entidadService.findById(entityId); // obtiene la entidad por ID desde el front es xxcntIdEntidadPk de la clase Entidad
            List<EntidadAtributoPadre> eapToPersist = new ArrayList();
            int i = 0;
            var entidadValorPadre = "";
            List<Long> idsConsultas = new ArrayList();
            //idsConsultas.add(((Atributo) ats.get(0)).getXxcntIdConsultaFk());
            idsConsultas.add(ats.get(0).getXxcntIdConsultaFk());
            List<Long> attrIdSet = getIdAttributeByQueryIdSet(idsConsultas);
            List<ValorAtributo> queriesVa = valorAtributoService.findValorAtributoByIdAttSet(attrIdSet);
            Long firstAttOfFirstQueryOfFirstDomain = -1L;
            for (Atributo a : ats) {
                if (isFirstAttributeOfFirstQueryOfFirstDomain(a.getXxcntIdAtributoPk())) {
                    firstAttOfFirstQueryOfFirstDomain = a.getXxcntIdAtributoPk();
                    break;
                }
            }
            for (String row : fRow) {
                if (i == 0) {
                    i++;
                } else {
                    for (int index = 0; index < row.split(":::").length; index++) {
                        Atributo currentAtt = ats.get(index);
                        Object[] existsValnDb = valorAtributoService.existsValueInMemory(row.split(":::")[index], currentAtt.getXxcntIdAtributoPk(), queriesVa);

                        ValorAtributo valnDB = null;

                        String vaInDBAuxS = "";
                        if ((Boolean) existsValnDb[0]) {
                            valnDB = queriesVa.get((Integer) existsValnDb[1]);
                            vaInDBAuxS = valnDB.getValorAtributo() != null ? valnDB.getValorAtributo() : "";
                        }
                        String col = row.split(":::")[index];
                        if (!(Boolean) existsValnDb[0]) {
                            ValorAtributo va = new ValorAtributo();
                            va.setEntidadAtributo(row.split(":::")[0]);
                            va.setFechaCreacion(new Date());
                            va.setFechaModificacion(new Date());
                            va.setUsuarioCreacion("UsuarioCNT");
                            va.setUsuarioModificacion("UsuarioCNT");
                            va.setValorAtributo(col);
                            if (currentAtt.getXxcntIdTipoDatoFk() == 1L) {
                                try {
                                    va.setValorAtributoNumero(BigDecimal.valueOf(Double.parseDouble(col)));
                                } catch (NumberFormatException nfe) {
                                    va.setValorAtributoNumero(null);
                                }
                            } else if (currentAtt.getXxcntIdTipoDatoFk() == 3L) {
                                try {
                                    va.setValorAtributoFecha(new SimpleDateFormat("dd/MM/yyyy").parse(col));
                                } catch (ParseException ex) {
                                    Logger.getLogger(AtributoController.class.getName()).log(Level.SEVERE, null, ex);
                                } finally {
                                    va.setValorAtributoFecha(null);
                                }
                            }
                            boolean isFirst = currentAtt.getXxcntIdAtributoPk().longValue() == firstAttOfFirstQueryOfFirstDomain.longValue();
                            if (isFirst) {
                                long consultaFK = currentAtt.getXxcntIdConsultaFk();
                                long me = e.getXxcntIdEntidadPk();
                                Long parent = e.getEntidadPadre();
                                long fParent = parent == 0L ? me : parent;

                                entidadAtributoPadreService.cleanByIdParentIdAndEntityValue(me, fParent, col);
                                boolean hasParent = (e.getEntidadPadre() != null) && (e.getEntidadPadre() != 0L);
                                int[] parentIndexes = indexesOfParentAndUserValues(consultaFK, hasParent, ats);
                                EntidadAtributoPadre eap = new EntidadAtributoPadre();
                                eap.setEntidadId(me);
                                eap.setEntidadPadreId(fParent);
                                try {
                                    eap.setValorEntidadPadre(row.split(":::")[parentIndexes[0]]);
                                } catch (NullPointerException npe) {
                                    eap.setValorEntidadPadre(col);
                                }
                                eap.setValorEntidad(col);
                                Object pv = null;
                                try {
                                    pv = row.split(":::")[parentIndexes[1]];
                                } catch (NullPointerException npe) {
                                }
                                String s = pv != null ? pv.toString() : "";
                                eap.setValorUsuario(s);
                                if (eapToPersist == null) {
                                    eapToPersist = new ArrayList(); // se inicializa pero jamas se usa en el metodo
                                }
                                entidadValorPadre = eap.getValorEntidadPadre();
                                entidadAtributoPadreService.addParentEntityAttribute(eap);
                            } else {
                                entidadValorPadre = entidadAtributoPadreService.findParentValueByEntityValueAndEntityId(va.getEntidadAtributo(), e.getXxcntIdEntidadPk());
                            }
                            va.setXxcntIdAtributoFk(currentAtt.getXxcntIdAtributoPk());
                            va.setEntidadValorPadre(entidadValorPadre);
                            this.valorAtributoService.addValorAtributo(va);
                        } else if (!col.equals(vaInDBAuxS)) {
                            ValorAtributoRespaldo var = getValorAtributoRespaldo(valnDB);
                            valorAtributoRespaldoService.create(var);
                            valnDB.setValorAtributo(col);
                            this.valorAtributoService.update(valnDB);
                        }
                    }
                }
            }
            log.info("Procesamiento de archivo finalizado");

        } else {
            log.info("No se recibió archivo");
        }
    } catch (IOException e) {
        log.error("Error al procesar los atributos", e);
        throw new IOException("Error al procesar los atributos" + e.getMessage(),e);
    }
    log.info("Fin del método processAtributos");
    return "exito";
}

Postman配置

Method: POST
URL: [ENDPOINT_URL]
Body: form-data
Key: listDTO
Value: [JSON_OF_DTO_LIST]
Key: file
Value: [FILE]

原因分析

  1. 参数名不匹配:控制器中@RequestPart的参数名为atributoDtoList,但Postman中form-data的Key是listDTO,Spring无法正确映射该参数,导致请求解析异常。
  2. Content-Type未指定:发送form-data时,DTO列表的part未设置application/json的Content-Type,Spring默认按application/octet-stream处理,无法将流解析为List<AtributoDto>,触发415错误。
  3. @RequestBody与文件冲突:@RequestBody只能接收纯JSON请求体,无法同时处理文件上传,因此切换后文件部分仍报错。

解决办法

1. 对齐参数名

将Postman中form-data的Key从listDTO改为atributoDtoList,与控制器参数名保持一致。

2. 指定DTO列表的Content-Type

在Postman中,点击atributoDtoList对应的Content-Type选项,设置为application/json,确保Spring能识别该部分为JSON数据并正确解析。

3. 显式声明请求类型

在控制器的@PostMapping中添加consumes属性,明确支持multipart请求:

@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)

4. 验证DTO序列化配置

确保AtributoDto类(或record)的字段与JSON键匹配,且Jackson能正常序列化/反序列化该类。若为普通类,需提供正确的getter/setter方法。

验证步骤

  1. 修改Postman的form-data Key为atributoDtoList,并设置该part的Content-Type为application/json。
  2. 确认file part的Content-Type由Postman自动匹配文件类型。
  3. 重新发送请求,检查是否能正常接收DTO列表与文件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 18:15:54