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]
原因分析
- 参数名不匹配:控制器中
@RequestPart的参数名为atributoDtoList,但Postman中form-data的Key是listDTO,Spring无法正确映射该参数,导致请求解析异常。 - Content-Type未指定:发送form-data时,DTO列表的part未设置
application/json的Content-Type,Spring默认按application/octet-stream处理,无法将流解析为List<AtributoDto>,触发415错误。 - @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方法。
验证步骤
- 修改Postman的form-data Key为
atributoDtoList,并设置该part的Content-Type为application/json。 - 确认file part的Content-Type由Postman自动匹配文件类型。
- 重新发送请求,检查是否能正常接收DTO列表与文件。
内容的提问来源于stack exchange,提问作者valencia
相关产品推荐
相关产品推荐

