Como obter as dimensões de uma imagem (Base64) com Java dentro do MuleSoft

Como obter as dimensões de uma imagem com Java no Mule 4

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:

O Fluxo de ponta a ponta

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 — retorna null. Por isso a checagem if (image == null) é essencial; sem ela, a próxima linha estouraria com um NullPointerException difí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 method inclui 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 curta getDimensions(String) também funciona).
  • O argumento usa o nome do parâmetro (base64), o que depende da flag -parameters na compilação — ativa por padrão nos projetos do Studio. Sem ela, use arg0 no 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.

Imagem do fluxo no mulesoft
<?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:

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *