Pouca gente fala, mas nenhum framework Java web tradicional (nem mesmo Spring) suporta de forma nativa multipart/mixed.
Em um projeto onde era necessário enviar JSON + arquivos no mesmo request (padrão moderno em APIs RESTful), o Tomcat simplesmente quebrava. VRaptor? Nem passava dos filtros.
A solução? Esse filtro intercepta requisições multipart/mixed, duplica o corpo da requisição e entrega um novo ServletInputStream para que o restante da aplicação funcione normalmente — como se fosse suportado nativamente.
Isso desbloqueia:
Integração com APIs modernas
Uploads + metadados num só request
Logging de payloads completos
Parsing customizado mesmo em ambientes legados
Recentemente, enfrentei uma integração com dispositivos Intelbras que envia requisições com o cabeçalho Content-Type: multipart/mixed. Como o InputStream de uma requisição HTTP pode ser lido apenas uma vez, precisei implementar um filtro para capturar e duplicar o corpo da requisição, garantindo que ele pudesse ser reutilizado por outros filtros e controllers no fluxo da aplicação.
Esse filtro intercepta rotas específicas, armazena o corpo da requisição em memória, e o repassa via HttpServletRequestWrapper, utilizando um novo ServletInputStream baseado em ByteArrayInputStream.
Incompatíveis com multipart/mixed (sem suporte nativo):
VRaptor 3.x
Não reconhece multipart/mixed, apenas multipart/form-data
Documentação + issues + testes
Spring MVC (v5)
Suporte limitado apenas a multipart/form-data
Documentação Spring Multipart
Jakarta REST (JAX-RS)
Sem suporte nativo a multipart/mixed
Documentação Jakarta REST
Tomcat
getParts() não funciona corretamente com multipart/mixed
Documentação Tomcat
ASP.NET Core
Não reconhece multipart/mixed como formato de upload padrão
Documentação ASP.NET
Flask
Sem suporte embutido para multipart/mixed
Documentação Flask
Django
Limitado a multipart/form-data via request.FILES
Documentação Django
Node.js (Express + multer)
Multer não lida com multipart/mixed
Documentação Multer/Node.js
PHP puro
Suporte apenas a multipart/form-data via $_FILES
Documentação PHP
Solução no VRaptor 3.x
VRaptor 3.x – Limitações com multipart/mixed
Fonte: Documentação oficial do VRaptor 3, histórico de issues e testes práticos.
Diagnóstico
O VRaptor 3.x possui suporte nativo apenas para multipart/form-data, utilizando o Servlet3MultipartInterceptor.
O tipo multipart/mixed não é reconhecido nem tratado pelo framework, o que inviabiliza seu uso em uploads com múltiplas partes heterogêneas (ex: arquivos + JSON no mesmo corpo).
Limitações técnicas identificadas
O interceptor atual depende do request.getParts(), que não processa multipart/mixed.
Não há documentação ou trechos no código que tratem multipart/mixed.
VRaptor 3 já apresenta restrições com o próprio multipart/form-data em containers como Tomcat 7 segundo publicações.
Assumindo um projeto padrão Maven com VRaptor 3.x ou qualquer app Java Web:
src/
└── main/
├── java/
│ └── com/
│ └── suaempresa/
│ └── seuprojeto/
│ └── filter/
│ └── MultipartMixedFilter.java
├── resources/
└── webapp/
└── WEB-INF/
└── web.xml
Classe: MultipartMixedFilter.java
// Importações básicas para manipulação de streams, datas e exceções
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
// Importações de classes da API Servlet
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
// Anotação que registra o filtro para todas as requisições que começam com /api/exemplo
@WebFilter(urlPatterns = "/api/exemplo/*")
public class MultipartMixedFilter implements Filter {
// Método chamado quando o filtro é inicializado — não estamos usando aqui
@Override
public void init(FilterConfig filterConfig) {}
// Método principal do filtro, intercepta e processa a requisição
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// Faz o cast para HttpServletRequest para ter acesso a métodos específicos da API HTTP
HttpServletRequest httpRequest = (HttpServletRequest) request;
// Obtém o tipo de conteúdo da requisição
String contentType = httpRequest.getContentType();
// Verifica se é uma requisição multipart/mixed
if (contentType != null && contentType.contains("multipart/mixed")) {
// Cria um buffer para armazenar o corpo da requisição
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
// Lê o corpo da requisição original e grava no buffer
try (InputStream inputStream = httpRequest.getInputStream()) {
byte[] temp = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(temp)) != -1) {
buffer.write(temp, 0, bytesRead);
}
}
// Transforma o buffer em um array de bytes
byte[] bodyBytes = buffer.toByteArray();
// Cria um wrapper para a requisição original, permitindo reutilização do InputStream
HttpServletRequest wrappedRequest = new HttpServletRequestWrapper(httpRequest) {
// Sobrescreve o getInputStream para retornar o corpo armazenado
@Override
public ServletInputStream getInputStream() {
return new ServletInputStream() {
private final ByteArrayInputStream bais = new ByteArrayInputStream(bodyBytes);
@Override
public int read() {
return bais.read(); // Retorna byte a byte
}
@Override
public boolean isFinished() {
return bais.available() == 0; // Fim do stream
}
@Override
public boolean isReady() {
return true; // Sempre pronto pra leitura
}
@Override
public void setReadListener(ReadListener readListener) {
// Não implementado (poderia ser usado para leitura assíncrona)
}
};
}
// Garante que o content length da nova requisição seja compatível
@Override
public int getContentLength() {
return bodyBytes.length;
}
@Override
public long getContentLengthLong() {
return bodyBytes.length;
}
};
// Passa a requisição "wrappeada" adiante na cadeia de filtros
chain.doFilter(wrappedRequest, response);
return;
}
// Se não for multipart/mixed, continua o fluxo normalmente
chain.doFilter(request, response);
}
// Método chamado na destruição do filtro — não estamos usando
@Override
public void destroy() {}
}
Classe: web.xml (Adicionar no web.xml)
<!--
Definição do filtro MultipartMixedFilter.
Esse filtro permite que o corpo da requisição multipart/mixed (como uploads de arquivos)
seja lido mais de uma vez, o que é útil em logs, debug, validações ou integrações.
-->
<filter>
<!-- Nome identificador do filtro -->
<filter-name>MultipartMixedFilter</filter-name>
<!-- Caminho completo (fully qualified) da classe Java que implementa o filtro -->
<filter-class>com.suaempresa.seuprojeto.filter.MultipartMixedFilter</filter-class>
</filter>
<!--
Mapeamento do filtro para interceptar todas as requisições da aplicação.
Aqui você está dizendo que o filtro será executado para qualquer URL que entrar no sistema.
-->
<filter-mapping>
<!-- Nome do filtro definido acima -->
<filter-name>MultipartMixedFilter</filter-name>
<!-- Padrão de URL que esse filtro vai interceptar.
/* significa que será aplicado globalmente a todas as rotas -->
<url-pattern>/*</url-pattern>
</filter-mapping>
Impacto real do MultipartMixedFilter no projeto
Benefícios práticos
1. Permite reprocessamento do corpo da requisição
- O HttpServletRequest.getInputStream() só pode ser lido uma vez por padrão.
- Esse filtro “clona” o corpo (bodyBytes) e permite múltiplas leituras.
- Útil para logs, validações de segurança, auditoria, etc.
2. Suporte a requisições complexas (ex: multipart/mixed)
- Algumas APIs externas, especialmente integradas com sistemas legados ou gateways, usam multipart/mixed (em vez de multipart/form-data).
- O filtro garante que sua aplicação aceite e trate corretamente esse tipo de conteúdo.
3. Evita quebra em filtros, interceptadores ou logs
- Sem ele, se outro filtro ler o InputStream, o controller depois não consegue mais acessar.
- Com esse filtro, você blinda o comportamento.
Riscos e cuidados
1. Mais uso de memória
- Toda requisição com multipart/mixed é armazenada em memória RAM (byte array).
- Requisições grandes (>10MB) podem causar estouro de memória (OOM) sob alta carga.
- Se a aplicação processa arquivos ou JSONs muito grandes, precisa limitar o tamanho.
2. Potencial latência maior
- A leitura do input inteiro e cópia para buffer pode aumentar o tempo de resposta.
- Impacto baixo na maioria dos casos, mas relevante em alta escala.
3. Pode interferir com outros filtros
- Se outro filtro também tenta ler/modificar o input, pode haver conflito.
- Recomendado que este filtro seja o primeiro da cadeia.
Situação
Usar o filtro?
Precisa logar o body da requisição
Sim
Usa APIs com multipart/mixed
Sim
Precisa ler o body no filtro e no controller
Sim
Só processa application/json comum
Não precisa
Processa arquivos grandes (>10MB)
Com limite
Rodando em ambiente com pouca RAM
Avaliar uso
E você? Já lidou com integrações que usam multipart/mixed?
Como resolveu?
Teve que contornar limitações no framework também?
Tem libs, ou exemplos práticos que usou?