Um caso real de integração entre AWS S3 e Salesforce, onde o DataWeave travou e uma classe Java de poucas linhas resolveu. Segundo artigo da série sobre Java e low-code no Mule.
No primeiro artigo desta série, vimos como o Java Module funciona no Mule 4 — criar objetos, invocar métodos e usar utilitários estáticos — usando uma API de cálculo de frete como exemplo. Aquele era um cenário didático, montado para mostrar cada operação.
Agora vamos a um caso de verdade, daqueles que aparecem no dia a dia de quem integra sistemas: buscar uma imagem em um bucket da AWS S3 e enviá-la para o Salesforce, registrando junto as dimensões dessa imagem. Parece simples — e quase tudo era, menos um detalhe que fez um time inteiro travar.
O desafio
A tarefa completa tinha quatro etapas: buscar o arquivo de imagem no S3, descobrir as dimensões dele (largura e altura), enviar o arquivo para o Salesforce e, por fim, gravar essas dimensões em um objeto. Três dessas quatro etapas são trabalho de conector — exatamente o que o Mule faz bem. O problema morava na segunda.
A imagem, depois de lida do S3, estava disponível como uma string Base64 (a imagem “virada texto”). E a exigência era obter a largura e a altura dela a partir dessa string, antes de seguir para o Salesforce.
No Mule, a primeira tentativa natural é resolver tudo no DataWeave. E foi o que o time tentou. O DataWeave é excelente para transformar dados estruturados — mapear campos, converter formatos, filtrar listas. Mas “descobrir as dimensões de uma imagem a partir do Base64” não é uma transformação de dados estruturados: é parsing de dados binários. É preciso decodificar o Base64 de volta para bytes e interpretar o cabeçalho do formato da imagem (PNG, JPEG, etc.), onde cada formato guarda largura e altura num lugar diferente.
Esse tipo de manipulação binária está fora do propósito do DataWeave. O time passou um tempo tentando forçar uma solução, que ia ficando cada vez mais complexa e frágil — um sinal clássico de que a ferramenta estava sendo usada para algo que ela não foi feita para fazer.
A virada foi reconhecer isso e resolver a etapa problemática com Java, dentro do próprio Mule, usando o Java Module. O resto do fluxo continuou tal como estava — conectores nativos para o S3 e o Salesforce.
O fluxo de ponta a ponta
Antes de entrar no código, vale ver o desenho completo da integração. As quatro etapas, em sequência:

Repare na divisão por cor: as etapas de buscar no S3, fazer upload no Salesforce e gravar o registro final são resolvidas por conectores nativos do Mule. Apenas a segunda etapa — extrair as dimensões a partir do Base64 — desce para o Java. É uma intervenção cirúrgica: um único ponto do fluxo onde o código entra para fazer o que o low-code não faz bem, sem substituir nada do que o Mule já resolve com elegância.
A classe Java que resolve o problema
A classe é pequena e usa apenas a biblioteca padrão do Java — nada de dependências externas. São duas peças: java.util.Base64 para decodificar a string de volta em bytes, e javax.imageio.ImageIO para ler esses bytes como imagem e entregar largura e altura prontas.
package com.integradordedados.examples;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.util.Base64;
import javax.imageio.ImageIO;
public class ImageDimensionsReader {
public static class ImageDimensions {
private final int width;
private final int height;
public ImageDimensions(int width, int height) {
this.width = width;
this.height = height;
}
public int getWidth() { return width; }
public int getHeight() { return height; }
}
public static ImageDimensions getDimensions(String base64) {
if (base64 == null || base64.trim().isEmpty()) {
throw new IllegalArgumentException("A string Base64 não pode ser nula ou vazia.");
}
// Remove o prefixo data URI, se presente (ex: "data:image/png;base64,...")
String pureBase64 = base64.contains(",")
? base64.substring(base64.indexOf(',') + 1)
: base64;
byte[] imageBytes;
try {
imageBytes = Base64.getDecoder().decode(pureBase64.trim());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("String Base64 inválida: " + e.getMessage(), e);
}
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes)) {
BufferedImage image = ImageIO.read(inputStream);
if (image == null) {
throw new IllegalArgumentException(
"Não foi possível decodificar a imagem. Verifique se o Base64 representa uma imagem válida.");
}
return new ImageDimensions(image.getWidth(), image.getHeight());
} catch (IllegalArgumentException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException("Erro ao processar a imagem: " + e.getMessage(), e);
}
}
}
Três pontos dessa classe merecem destaque, porque são o que a torna robusta o suficiente para produção:
A biblioteca padrão já resolvia. Não foi preciso instalar nada. O ImageIO.read identifica o formato da imagem e devolve um BufferedImage que já expõe getWidth() e getHeight(). Todo o trabalho difícil — que era inviável no DataWeave — já vinha pronto no ecossistema Java. Esse costuma ser o melhor motivo para descer ao código: a solução já existe lá.
O código trata o caso real, não só o ideal. A string Base64 às vezes vem “pura”, às vezes com o prefixo data:image/png;base64,. O código lida com os dois, valida entrada nula e o caso de o conteúdo não ser uma imagem de verdade. Em integração, o dado quase nunca vem do jeito que você espera — tratar isso é o que separa código de demonstração de código de produção.
O escopo é cirúrgico. A classe faz uma coisa só: recebe um Base64 e devolve as dimensões. Ela não busca no S3, não fala com o Salesforce. Tudo isso continua sendo responsabilidade do Mule.
Há um detalhe traiçoeiro escondido no
ImageIO.read: se os bytes não forem de uma imagem reconhecível, ele não lança exceção — retornanull. Por isso a checagemif (image == null)é essencial; sem ela, a próxima linha estouraria com umNullPointerExceptiondifícil de diagnosticar.
Chamando a classe no fluxo Mule
Como o método getDimensions é public static, a chamada no Mule usa a operação Invoke static do Java Module — a mais simples, que não exige criar uma instância. (Se precisar de uma recapitulação de como o Java Module funciona, o primeiro artigo da série cobre isso em detalhe.)
A chamada em si é direta. Supondo que o Base64 da imagem esteja no payload:
<java:invoke-static
class="com.integradordedados.examples.ImageDimensionsReader"
method="getDimensions(java.lang.String)">
<java:args><![CDATA[#[{ base64: payload }]]]></java:args>
</java:invoke-static>
Pontos importantes:
- O
methodinclui o tipo do parâmetro —getDimensions(java.lang.String). A assinatura completa identifica qual método chamar (o Studio costuma gerar o tipo com o pacote completo, mas a forma curtagetDimensions(String)também funciona). - O argumento usa o nome do parâmetro (
base64), o que depende da flag-parametersna compilação — ativa por padrão nos projetos do Studio. Sem ela, usearg0no lugar.
Um exemplo que você pode rodar localmente
O caso real usa S3 e Salesforce, mas para testar a chamada da classe você não precisa de nenhum dos dois. Dá para montar um fluxo mínimo que lê imagens de uma pasta local, extrai as dimensões e move o arquivo para uma pasta processed — tudo na sua máquina, sem credenciais de nuvem. É uma ótima forma de validar a classe antes de plugá-la no fluxo de produção.

<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core"
xmlns:java="http://www.mulesoft.org/schema/mule/java"
xmlns:file="http://www.mulesoft.org/schema/mule/file"
xmlns="http://www.mulesoft.org/schema/mule/core"
xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/file http://www.mulesoft.org/schema/mule/file/current/mule-file.xsd
http://www.mulesoft.org/schema/mule/java http://www.mulesoft.org/schema/mule/java/current/mule-java.xsd
http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd">
<flow name="image-dimensions-utilsFlow"
doc:id="0808d48a-aded-4168-930c-9d2f544ca92a">
<file:listener doc:name="On New or Updated File"
doc:id="23203f06-70d9-4520-a349-9f00763a7157"
directory="C:\Mulesoft\FileModule"
moveToDirectory="C:\Mulesoft\FileModule\processed" recursive="false">
<scheduling-strategy>
<fixed-frequency />
</scheduling-strategy>
<file:matcher directories="EXCLUDE" />
</file:listener>
<ee:transform doc:name="toBase64"
doc:id="663a8394-c806-453b-9677-f5c3ade1e9f0">
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
import * from dw::core::Binaries
output text/plain
---
toBase64(payload as Binary)
]]></ee:set-payload>
</ee:message>
</ee:transform>
<java:invoke-static
method="getDimensions(java.lang.String)" doc:name="getDimensions()"
doc:id="50e535cd-eaf0-4f54-a067-608892e259e2"
class="com.integradordedados.examples.ImageDimensionsReader"
target="dimensions">
<java:args><![CDATA[#[{"base64": payload}]]]></java:args>
</java:invoke-static>
<ee:transform doc:name="payload"
doc:id="086d085c-4492-446c-9ca0-6c47e6c81167">
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{
width: vars.dimensions.width,
height: vars.dimensions.height
}]]></ee:set-payload>
</ee:message>
</ee:transform>
<logger level="INFO" doc:name="Logger" doc:id="f29daa6d-e5c4-4e75-aef6-5a3edde0694b" />
</flow>
</mule>
O fluxo tem três momentos que vale entender:
O File listener como gatilho. Ele fica observando a pasta C:\Mulesoft\FileModule e dispara o fluxo sempre que um arquivo novo aparece. O atributo moveToDirectory é um detalhe elegante: ao terminar o processamento, o Mule move automaticamente o arquivo para a subpasta processed, evitando reprocessar o mesmo arquivo e deixando claro o que já foi tratado. O file:matcher com directories="EXCLUDE" garante que subpastas não sejam tratadas como arquivos.
A conversão para Base64. Esse é o elo essencial entre o arquivo e a classe Java. O File listener entrega o conteúdo do arquivo como binário, mas o método getDimensions espera uma string Base64. O Transform Message faz exatamente essa ponte: toBase64(payload as Binary) — primeiro garante que o payload é tratado como binário, depois o converte na string Base64 que a classe sabe ler. A função toBase64 vem do módulo dw::core::Binaries, importado no início do script.
A chamada Java. Com o Base64 no payload, o invoke-static chama o método passando { base64: payload }. A chave base64 casa com o nome do parâmetro do método. Como aqui não usamos target, o retorno (o objeto ImageDimensions) vai para o próprio payload, e o Logger logo em seguida permite inspecioná-lo no console — útil para confirmar que largura e altura saíram certas antes de seguir.
No fluxo de produção (S3 → Salesforce), a única diferença real é a origem e o destino do arquivo: em vez do File listener, um conector S3 traz a imagem; em vez de mover para
processed, um conector Salesforce faz o upload e grava as dimensões. O miolo — converter para Base64 e chamar a classe — é idêntico. Por isso testar localmente vale tanto: o que você valida na pasta local é exatamente o que vai rodar na nuvem.
Usando o resultado
O invoke-static devolve o objeto ImageDimensions. O DataWeave lê suas propriedades pela convenção de getters — getWidth() vira width, getHeight() vira height. Se você quiser guardar o retorno numa variável (em vez de deixá-lo no payload, como no exemplo local acima), use o atributo target na operação. Então, na etapa de gravar o registro no Salesforce, você acessa as dimensões assim:
%dw 2.0
output application/json
---
{
width: vars.dimensions.width,
height: vars.dimensions.height
}
Esses valores são então mapeados para os campos do objeto no Salesforce, fechando a quarta e última etapa do fluxo.
Uma alternativa para simplificar o lado do DataWeave: fazer o próprio método Java retornar um
Map<String, Integer>com as chaves"width"e"height", em vez de um objeto tipado. O retorno chega como um mapa que o DataWeave lê sem cerimônia. É uma decisão de design — objeto tipado (mais limpo no Java) ou Map (mais direto no Mule). Para um utilitário usado só dentro do Mule, o Map costuma facilitar.
O que esse caso ensina
A lição não é “Java é melhor que DataWeave”. É mais sutil e mais útil: cada ferramenta tem um propósito, e reconhecer quando uma tarefa saiu do território de uma e entrou no da outra é uma habilidade central de quem arquiteta integrações.
O DataWeave continua sendo a escolha certa para transformar dados — e foi usado, inclusive, para montar o payload final do Salesforce. O conector do S3 e o do Salesforce fizeram o trabalho deles. O Java entrou em um único ponto, onde a natureza do problema (parsing binário) pedia uma linguagem de propósito geral com a biblioteca certa já pronta.
Quando você se pegar batendo cabeça numa transformação que teima em não sair no DataWeave, vale a pergunta: essa parte do problema é mesmo de transformação de dados, ou é de outra natureza? Às vezes a resposta mais elegante é descer algumas linhas para o Java — e deixar o resto do fluxo continuar sendo low-code.
Quer ir além do Java Module?
Dominar a integração entre Java e MuleSoft é uma peça importante, mas construir uma carreira sólida em integração de sistemas no mundo corporativo envolve muito mais. Foi para reunir esse caminho em um guia claro que escrevi o ebook “Do Zero à Engenharia de Integrações” — para quem quer entrar e evoluir na carreira de integrações entre sistemas, com MuleSoft no centro.
📘 Conheça o ebook “Do Zero à Engenharia de Integrações”
Referências
- Artigo anterior da série — Java Module no Mule 4: como usar Java dentro do seu fluxo: https://integradordedados.com.br/java-module-no-mule-4-como-usar-java-dentro-do-seu-fluxo/
- Código-fonte dos exemplos: https://github.com/Weslleyw10/mule-java-module-examples
- MuleSoft Documentation — Java Module 2.0: https://docs.mulesoft.com/java-module/latest/
- Vídeo no canal: https://www.youtube.com/@integradordedados
Compartilhe com sua rede:



