Custom Java Delegates
Nível de acesso requerido
Super Admin
O motor de workflow do Flowi Agentic (Flowable) suporta a execução de lógicas customizadas injetadas no meio dos processos através de Java Delegates. Diferente de integrações externas (Webhooks), os Delegates rodam nativamente dentro da memória do motor, garantindo performance e transações atômicas com o banco de dados.
Por que o upload é exclusivo do Super Admin
Um Java Delegate roda dentro da mesma máquina virtual da plataforma, e Java não oferece sandbox para código que executa no próprio processo. Na prática, um delegate alcança o banco de dados, as variáveis de ambiente e qualquer componente da aplicação — não existe configuração capaz de limitá-lo.
Por isso o upload é restrito ao Super Admin. Esse papel já possui acesso total à plataforma, então subir código não concede nenhum privilégio que ele ainda não tivesse: a fronteira de confiança coincide exatamente com o privilégio.
Delegates não são enviados por tenant, e isso é uma decisão de projeto, não uma etapa pendente. Código de tenant que precise rodar durante um processo pertence aos Robôs, que executam fora da máquina virtual da plataforma, sem credenciais de banco, conversando com o sistema apenas por RPC com escopo de um único tenant.
Upload do Delegate
- Acesse Global (Super Admin) → Java Delegates.
- Faça o upload do seu arquivo compilado
.jar. - O sistema fará o escaneamento automático e extrairá as classes que implementam a interface
JavaDelegate. - A tela mostra, na coluna
delegateExpression, a expressão exata de cada classe — por exemplo${parserNotaFiscal}. - No BPMN Modeler, quem desenha o processo escolhe o delegate numa lista, sem digitar nada — veja Etapa que chama um Java Delegate.
O nome do bean vem do @Component("nome") da classe. Sem a anotação, a plataforma usa o nome simples da classe com a inicial minúscula. Esse nome é o que aparece para o modelador, então vale escolhê-lo pensando em quem vai lê-lo no diagrama.
A lista de delegates só é lida por quem modela
Enviar JAR é do Super Admin; ler a lista (GET /a/delegates) exige Admin do tenant ou Super Admin. A resposta traz o nome da classe Java e os parâmetros declarados de cada bean — informação sobre como a instalação é feita por dentro — e quem consome é só o seletor do modelador, que já é tela de Admin. Gerente e usuário comum recebem 403.
Processo e caso pedem interfaces diferentes
JavaDelegate é a interface do motor BPMN. O motor de casos (CMMN) não a aceita: ele executa PlanItemJavaDelegate, PlanItemFutureJavaDelegate ou CmmnActivityBehavior. Uma mesma classe pode implementar as duas e servir aos dois motores.
A plataforma verifica isso ao carregar o JAR e registra, por bean, quais motores conseguem executá-lo. É essa marcação que faz o modelador CMMN listar só os delegates de caso e avisar quando o arquivo aponta para um delegate exclusivo de processo — antes, a escolha errada só aparecia como uma etapa parada em produção.
Essa lista mostra o que este tenant enviou. O delegate de caso da própria plataforma — a etapa de IA — não está nela; ele tem entrada própria na lista de implementações. Veja Agentes de IA.
Declarando parâmetros
Um delegate que sempre faz exatamente a mesma coisa não precisa de configuração. Um delegate útil normalmente precisa: qual campo ler, qual layout assumir, qual limite aplicar.
Declare cada parâmetro na classe com @DelegateParameter, do mesmo jeito que um robô usa @RobotParameter:
@Component("parserNotaFiscal")
@DelegateParameter(name = "layout", label = "Layout do XML", required = true, defaultValue = "nfse-v2")
public class ParserNotaFiscal implements JavaDelegate {
private Expression layout;
@Override
public void execute(DelegateExecution execution) {
String escolhido = (String) layout.getValue(execution);
}
}A plataforma lê a anotação durante o escaneamento do JAR, sem carregar a classe, e o modelador passa a ver um campo por parâmetro declarado. O valor chega ao delegate por injeção de campo do próprio Flowable: declare um campo Expression com o mesmo nome do parâmetro.
Parâmetro não guarda segredo
@DelegateParameter não tem secret, e isso é intencional. O valor preenchido pelo modelador fica dentro do XML do processo, que é versionado, exportável e legível por quem abre o diagrama. Marcar como secreto seria uma promessa que o armazenamento não cumpre. Para credenciais, leia as variáveis do tenant durante a execução.
De onde vem a anotação
@DelegateParameter e @RobotParameter vivem em backend/robot-runner, um módulo interno que não é publicado em repositório Maven. Não existe artefato para você declarar como dependência.
Isso é de propósito, e o caminho suportado é declarar a sua própria cópia. A plataforma casa a anotação pelo nome totalmente qualificado, lendo os bytes do JAR sem carregar a classe — ela nunca compara o tipo com o dela. Então uma cópia sua funciona igual, desde que o pacote e o nome batam exatamente:
package ia.flow.engine;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(DelegateParameters.class)
public @interface DelegateParameter {
String name();
String label() default "";
boolean required() default false;
String defaultValue() default "";
}O par repetível (DelegateParameters, com DelegateParameter[] value()) precisa existir junto, senão o compilador recusa a segunda anotação na mesma classe.
Por que não publicar um artefato
Publicar exigiria um repositório Maven acessível ao cliente, credenciais e uma promessa de compatibilidade de versão que ainda não faz sentido manter. Enquanto a anotação for cinco linhas sem comportamento, copiar é mais barato para os dois lados — e o casamento por nome garante que a cópia não envelhece.
O que a plataforma recusa
Nome de bean já usado pela plataforma. O upload é recusado nomeando a classe e o bean em conflito. Sobrescrever um componente do sistema em silêncio trocaria o comportamento da plataforma sem deixar rastro.
Deploy que referencia delegate inexistente. Ao publicar um BPMN que usa ${algumDelegate} sem esse delegate carregado, o deploy é recusado e a mensagem lista os delegates disponíveis. Sem essa recusa, o processo seria publicado, aceitaria trabalho e só falharia na etapa, como job repetindo sem fim.
flowable:class. Use sempre delegateExpression. A forma por nome de classe não resolve para JARs enviados por upload.
JAR que não carrega. Se as classes não puderem ser carregadas, o upload falha, o arquivo é descartado e a versão anterior continua ativa. Se a falha acontecer na subida da plataforma, o delegate aparece na tela marcado como Não carregado, em vez de parecer ativo.
Troca de versão
Enviar um novo JAR substitui o bean pelo da versão nova. Processos em andamento passam a usar a implementação nova na próxima etapa que a chamar — uma execução longa pode, portanto, atravessar duas versões.