Java Moderno com IA: LangChain4j, Quarkus, RAG e MCP na Prática Enterprise

java dev.to

O ecossistema Java está formalizando IA em produção. Este artigo apresenta os blocos concretos de construção para quem quer sair do protótipo e chegar ao contrato estável.


Contexto: o sinal que o ecossistema está dando

Antes de entrar no código, vale nomear o momento. Na sua newsletter de junho de 2026, o professor Elder Moraes — referência no ecossistema Java e criador do Método Java AI Specialist — sintetizou com precisão o que está acontecendo na plataforma:

"O ecossistema Java está colocando estrutura formal em volta do que já roda em produção: padrão de interop, governança de contribuição, default de framework."

— Elder Moraes, Newsletter Java Weekly, jun. 2026 (eldermoraes.ai)

A leitura do professor Moraes é precisa e serve como bússola para este artigo. O A2A Java SDK atingiu GA (1.0.0.Final), o Quarkus 3.37 ativou serialização Jackson livre de reflexão por padrão, e o próprio OpenJDK teve que legislar sobre código gerado por IA — um sinal de que a adoção já acontece em escala. O trabalho do desenvolvedor agora não é mais avaliar se usar IA em Java, mas saber como construir em cima do que já é contrato estável.

Este artigo trata exatamente disso: os três pilares concretos que você pode colocar em produção hoje — LangChain4j como camada de orquestração de LLM, RAG (Retrieval-Augmented Generation) como estratégia de contexto, e MCP (Model Context Protocol) como borda de tool e dados.


LangChain4j: o SDK que já conhece o seu stack

LangChain4j é a port Java do ecossistema LangChain, mas construída com idioma Java de verdade: anotações declarativas, integração nativa com Spring Boot e Quarkus, e suporte a múltiplos provedores de LLM (OpenAI, Anthropic, Ollama, Azure OpenAI, Bedrock, entre outros) trocáveis via configuração, sem alterar a lógica de negócio.

O conceito central é o AI Service: uma interface Java anotada que o framework implementa em tempo de build (ou runtime), abstraindo o ciclo completo de prompt, chamada ao modelo e parse da resposta.

// Declaração da interface — tudo que o dev precisa escrever
@RegisterAiService(retriever = EmbeddingStoreRetriever.class)
public interface DocumentAssistant {

    @SystemMessage("Você é um assistente especialista em regulatório financeiro.")
    @UserMessage("Com base nos documentos disponíveis, responda: {{question}}")
    String answer(@V("question") String question);
}
Enter fullscreen mode Exit fullscreen mode

Com Quarkus, a extensão quarkus-langchain4j injeta o serviço via CDI, resolve o provider de LLM pelo application.properties, e aplica os ganhos de native image automaticamente — incluindo o flip de Jackson reflection-free que o Quarkus 3.37 ativou por padrão, reduzindo cold start e consumo de heap sem nenhuma mudança no código da aplicação.

Configuração mínima com Quarkus + Ollama local

# application.properties
quarkus.langchain4j.ollama.base-url=http://localhost:11434
quarkus.langchain4j.ollama.chat-model.model-id=llama3
quarkus.langchain4j.ollama.embedding-model.model-id=nomic-embed-text
quarkus.langchain4j.ollama.timeout=60s
Enter fullscreen mode Exit fullscreen mode

Para produção, troca-se o bloco ollama por openai ou anthropic sem tocar em nenhuma linha Java. Esse isolamento é o primeiro contrato estável que o arquiteto precisa firmar: a lógica de negócio nunca deve conhecer o provedor de LLM.


RAG: contexto sem alucinação

Modelos de linguagem sabem muito, mas não sabem nada sobre os seus dados. RAG — Retrieval-Augmented Generation — é o padrão que resolve isso: em vez de treinar ou fazer fine-tuning (caro e lento), você recupera os fragmentos mais relevantes dos seus próprios dados no momento da pergunta e os injeta no contexto do prompt.

O pipeline tem três etapas fixas: ingestão (parse e chunking dos documentos), indexação (geração de embeddings e armazenamento em vector store) e recuperação (busca por similaridade no momento da query). LangChain4j cobre todas as três com APIs unificadas.

Pipeline de ingestão

@ApplicationScoped
public class DocumentIngestionService {

    @Inject EmbeddingModel embeddingModel;
    @Inject EmbeddingStore<TextSegment> embeddingStore;

    public void ingest(Path filePath) {
        // 1. Carrega o documento (PDF, TXT, HTML...)
        Document document = FileSystemDocumentLoader.loadDocument(filePath);

        // 2. Divide em chunks com overlap para preservar contexto
        DocumentSplitter splitter = DocumentSplitters
            .recursive(512, 64, new OpenAiTokenizer());

        // 3. Gera embeddings e persiste na vector store
        EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
            .documentSplitter(splitter)
            .embeddingModel(embeddingModel)
            .embeddingStore(embeddingStore)
            .build();

        ingestor.ingest(document);
    }
}
Enter fullscreen mode Exit fullscreen mode

Recuperação no momento da query

@ApplicationScoped
public class EmbeddingStoreRetriever implements ContentRetriever {

    @Inject EmbeddingModel embeddingModel;
    @Inject EmbeddingStore<TextSegment> embeddingStore;

    @Override
    public List<Content> retrieve(Query query) {
        Embedding queryEmbedding = embeddingModel
            .embed(query.text()).content();

        List<EmbeddingMatch<TextSegment>> matches =
            embeddingStore.findRelevant(queryEmbedding, 5, 0.75);

        return matches.stream()
            .map(m -> Content.from(m.embedded()))
            .toList();
    }
}
Enter fullscreen mode Exit fullscreen mode

Para ambientes enterprise com Oracle ou PostgreSQL (via pgvector), o EmbeddingStore troca de implementação pela configuração — nenhuma linha de recuperação muda. Em produção com base Oracle, o Oracle AI Vector Search (disponível desde o Oracle 23ai) é uma opção que elimina um componente de infraestrutura extra.

"A regra de decisão que eu usaria é separar o que é contrato estável pra construir em cima do que ainda é empolgante-mas-instável."

— Elder Moraes, Newsletter Java Weekly, jun. 2026 (eldermoraes.ai)

Aplicando a regra do professor Moraes ao RAG: o padrão de pipeline (ingestão, embedding, busca por similaridade, augmented prompt) é contrato estável. O que ainda varia é a escolha de vector store e o tamanho ótimo de chunk para o seu domínio. Mexa no segundo, confie no primeiro.


MCP: a borda de ferramenta que o seu agente precisa

Model Context Protocol (MCP) é o padrão aberto, governado pela Anthropic e adotado pela indústria, para expor ferramentas e fontes de dados a modelos de linguagem de forma interoperável. Se o RAG resolve o problema de conhecimento (o que o modelo sabe), o MCP resolve o problema de ação (o que o modelo pode fazer).

A distinção entre o A2A (Agent2Agent, para comunicação agente-a-agente) e o MCP (para a borda de tool e dados) é fundamental para uma arquitetura limpa. Misturar os dois papéis em um único componente é o caminho mais curto para um sistema impossível de testar.

Servidor MCP em Quarkus

A extensão quarkus-mcp-server expõe beans CDI como ferramentas MCP com uma única anotação:

@ApplicationScoped
public class ContaCorrenteTools {

    @Inject ContaCorrenteRepository repository;

    @Tool("Consulta o saldo atual de uma conta corrente pelo número da conta")
    public SaldoResponse consultarSaldo(
        @P("Número da conta no formato XXXXXXX-D") String numeroConta) {

        return repository.findSaldo(numeroConta)
            .map(SaldoResponse::of)
            .orElseThrow(() -> new ContaNaoEncontradaException(numeroConta));
    }

    @Tool("Lista os últimos lançamentos de uma conta em um período")
    public List<LancamentoResponse> listarLancamentos(
        @P("Número da conta") String numeroConta,
        @P("Data inicial no formato yyyy-MM-dd") String dataInicio,
        @P("Data final no formato yyyy-MM-dd") String dataFim) {

        return repository.findLancamentos(numeroConta,
            LocalDate.parse(dataInicio), LocalDate.parse(dataFim));
    }
}
Enter fullscreen mode Exit fullscreen mode

O Quarkus registra automaticamente essas ferramentas no endpoint MCP da aplicação. Um agente externo — seja um assistente Claude, seja um orquestrador LangChain4j do próprio sistema — descobre e chama as ferramentas via protocolo padrão sem conhecer a implementação Java.

Consumindo ferramentas MCP a partir de um AI Service

@RegisterAiService(tools = McpToolProvider.class)
public interface AssistenteFinanceiro {

    @SystemMessage("""
        Você é um assistente financeiro cooperativista.
        Use as ferramentas disponíveis para consultar dados reais antes de responder.
        Nunca invente números. Se não tiver dados suficientes, diga claramente.
        """)
    String conversar(@MemoryId String sessionId,
                     @UserMessage String mensagem);
}
Enter fullscreen mode Exit fullscreen mode

O ciclo completo fica assim: o usuário envia uma mensagem, o LLM decide quais ferramentas chamar, o Quarkus executa as chamadas MCP contra os seus próprios endpoints Java, os resultados retornam ao LLM como contexto, e a resposta final é gerada com dados reais. Tudo rastreável, tudo testável.


Juntando os três: arquitetura de referência

O fluxo completo de uma pergunta do usuário até a resposta com dados reais e contexto documental:

Usuário
  |
  v
[AssistenteFinanceiro — AI Service]
  |
  |-- (1) Recuperação RAG --> EmbeddingStoreRetriever
  |                               |
  |                           Vector Store
  |                               |
  |                           Fragmentos relevantes <--|
  |
  |-- (2) Tool calls via MCP --> ContaCorrenteTools
  |                               |
  |                           Repository / Oracle DB
  |                               |
  |                           Dados transacionais <--|
  |
  v
[LLM: Prompt = contexto RAG + dados MCP + histórico de sessão]
  |
  v
Resposta fundamentada
Enter fullscreen mode Exit fullscreen mode

Cada camada tem uma responsabilidade única. O AI Service orquestra. O RAG fornece conhecimento documental. O MCP fornece dados transacionais em tempo real. O LLM sintetiza. Nenhuma camada precisa conhecer as outras diretamente — apenas o contrato de interface.


A questão que ninguém está fazendo: governança de código gerado por IA

Há um aspecto que vai além da arquitetura técnica e que o professor Moraes nomeia com precisão na sua newsletter: a oportunidade real não está em adotar o framework mais novo, mas em ser o desenvolvedor que sabe governar o uso de IA no time.

A decisão do OpenJDK de barrar contribuições geradas por LLM — mantendo o uso privado para entender, debugar e revisar — e a decisão oposta da GraalVM ilustram que não existe uma resposta única. O que existe é a necessidade de uma política deliberada. Para um time que usa assistentes de IA no dia a dia, as perguntas práticas são:

  • Quais artefatos gerados por IA exigem revisão humana obrigatória antes do merge?
  • Como o time documenta a origem de um trecho crítico de lógica de negócio?
  • O pipeline de CI valida cobertura de testes mesmo quando o código é gerado automaticamente?
  • Existe um critério explícito para o que é "plausível mas errado" — o risco que o OpenJDK identificou como custo real de review?

Essas perguntas têm respostas técnicas (regras de linter, gates no pipeline, cobertura mínima), mas a decisão de fazer as perguntas é liderança de engenharia.


O que construir agora e o que esperar

Voltando à divisão que o professor Moraes propõe — contrato estável versus empolgante-mas-instável:

Construa agora:

  • AI Service com LangChain4j declarativo
  • Pipeline RAG com vector store já disponível na sua infraestrutura (Oracle ou Postgres)
  • Exposição de ferramentas via MCP a partir de beans CDI existentes
  • Política interna de governança de código gerado por IA

Acompanhe, não deploya ainda:

  • A2A SDK (GA, mas o ecossistema de frameworks ao redor ainda está em movimento)
  • Project Valhalla value classes (preview no JDK 28, provavelmente ainda em preview no próximo LTS)
  • TLS pós-quântico do JDK 27 (meça latência de handshake e compatibilidade com load balancers antes de assumir que vem de graça)

O ecossistema Java tem histórico de levar tempo para padronizar e, quando padroniza, padronizar de forma que dura décadas. O desenvolvedor que entender os contratos estáveis hoje vai construir sobre fundação sólida enquanto o restante ainda debate qual framework adotar.


Referências

Source: dev.to

arrow_back Back to Tutorials