DEV Community

mgd43b for AgentEnsemble

Posted on • Originally published at agentensemble.net

From CrewAI to Java: Multi-Agent Orchestration Without Python

You've seen the CrewAI demos. Maybe you've prototyped something with AutoGen or LangGraph. The concepts make sense -- agents with roles, tasks with dependencies, tools for grounding -- but there's a problem.

Your production stack is Java.

Your team writes Java. Your CI runs Gradle. Your monitoring is Micrometer and Prometheus. Your deployment is a fat JAR on Kubernetes. And now someone's proposing a Python sidecar for the AI agent layer, with a REST API in between, and a second dependency tree, and a second set of runtime semantics.

There's a better path. AgentEnsemble is a Java 21 framework that covers the same concepts as CrewAI -- agents, tasks, crews/ensembles, tools, workflows -- but runs natively on the JVM. Here's a concept-by-concept mapping.

The Concept Map

CrewAI (Python) AgentEnsemble (Java)
Agent Agent
Task Task
Crew Ensemble
Tool Tool (via @Tool annotation or AgentTool interface)
Process.sequential Workflow.SEQUENTIAL (or inferred)
Process.hierarchical Workflow.HIERARCHICAL
-- Workflow.PARALLEL (DAG-based)
-- MapReduceEnsemble
@tool decorator @Tool annotation on methods
Pydantic model output Java record outputType()
Callbacks EnsembleListener / lambda callbacks
Memory MemoryStore (in-memory, cross-run)

The core mental model is the same. The execution model, type safety, and production tooling are where they diverge.

Side by Side: A Research-Writer Pipeline

CrewAI (Python)

from crewai import Agent, Task, Crew, Process

researcher = Agent(
    role="Senior Researcher",
    goal="Find comprehensive information about {topic}",
    backstory="Expert at finding and synthesizing information",
)

writer = Agent(
    role="Technical Writer",
    goal="Write clear, engaging content",
    backstory="Skilled at making complex topics accessible",
)

research_task = Task(
    description="Research {topic} thoroughly",
    expected_output="Comprehensive research notes",
    agent=researcher,
)

write_task = Task(
    description="Write an article based on the research",
    expected_output="A polished article",
    agent=writer,
)

crew = Crew(
    agents=[researcher, writer],
    tasks=[research_task, write_task],
    process=Process.sequential,
)

result = crew.kickoff(inputs={"topic": "quantum computing"})
print(result)
Enter fullscreen mode Exit fullscreen mode

AgentEnsemble (Java)

Agent researcher = Agent.builder()
    .role("Senior Researcher")
    .goal("Find comprehensive information about {{topic}}")
    .background("Expert at finding and synthesizing information")
    .build();

Agent writer = Agent.builder()
    .role("Technical Writer")
    .goal("Write clear, engaging content")
    .background("Skilled at making complex topics accessible")
    .build();

Task researchTask = Task.builder()
    .description("Research {{topic}} thoroughly")
    .expectedOutput("Comprehensive research notes")
    .agent(researcher)
    .build();

Task writeTask = Task.builder()
    .description("Write an article based on the research")
    .expectedOutput("A polished article")
    .agent(writer)
    .context(List.of(researchTask))
    .build();

EnsembleOutput output = Ensemble.builder()
    .agents(researcher, writer)
    .tasks(researchTask, writeTask)
    .chatLanguageModel(model)
    .inputs(Map.of("topic", "quantum computing"))
    .build()
    .run();

System.out.println(output.getRaw());
Enter fullscreen mode Exit fullscreen mode

Nearly identical structure. The Java version uses builders instead of constructors, {{topic}} instead of {topic}, and makes dependencies explicit via context() rather than relying on task ordering.

What You Gain by Staying on the JVM

The mapping above shows conceptual parity. Here's what you gain beyond that.

1. Compile-Time Type Safety

In CrewAI, structured output uses Pydantic models:

from pydantic import BaseModel

class MovieReview(BaseModel):
    title: str
    rating: int
    summary: str

task = Task(
    description="Review the movie",
    expected_output="A movie review",
    output_pydantic=MovieReview,
)
Enter fullscreen mode Exit fullscreen mode

In AgentEnsemble, it's a Java record:

record MovieReview(String title, int rating, String summary) {}

Task task = Task.builder()
    .description("Review the movie '{{movie}}'")
    .expectedOutput("A movie review")
    .outputType(MovieReview.class)
    .build();

// Later:
MovieReview review = output.getTaskOutputs().get(0)
    .getStructuredOutput(MovieReview.class);
Enter fullscreen mode Exit fullscreen mode

Both work. But the Java version catches type mismatches at compile time. If you refactor MovieReview to rename a field, every access site that uses the old name is a compilation error, not a runtime AttributeError.

2. Parallel DAG Workflows

CrewAI supports sequential and hierarchical processes. AgentEnsemble adds parallel DAG execution:

// These run concurrently
Task marketResearch = Task.builder()
    .description("Analyze market trends")
    .agent(marketAnalyst).build();

Task financialAnalysis = Task.builder()
    .description("Analyze financials")
    .agent(financialAnalyst).build();

// This waits for both to finish
Task synthesis = Task.builder()
    .description("Synthesize market and financial findings")
    .agent(strategist)
    .context(List.of(marketResearch, financialAnalysis))
    .build();
Enter fullscreen mode Exit fullscreen mode

No explicit workflow declaration needed. The framework infers parallel execution from the dependency graph. Independent tasks run concurrently on virtual threads; dependent tasks wait for their inputs.

3. Workflow Inference

In CrewAI, you always specify Process.sequential or Process.hierarchical. In AgentEnsemble, you can omit the workflow entirely:

Ensemble.builder()
    .agents(researcher, analyst, writer)
    .tasks(researchTask, analysisTask, reportTask)
    .chatLanguageModel(model)
    .build()
    .run();
Enter fullscreen mode Exit fullscreen mode

The framework examines context() declarations on each task and infers:

  • All tasks in a linear chain? Sequential.
  • Tasks with branching/merging dependencies? Parallel DAG.
  • Single task with multiple agents and no assignments? Hierarchical.

You can still declare a workflow explicitly when you want to be specific.

4. MapReduce Ensembles

For workloads that need to process a collection of items in parallel and aggregate the results, AgentEnsemble provides a dedicated MapReduceEnsemble:

MapReduceEnsemble.<String, String>builder()
    .items(List.of("Chapter 1", "Chapter 2", "Chapter 3"))
    .mapAgentFactory(chapter -> Agent.builder()
        .role("Editor for " + chapter)
        .goal("Edit " + chapter + " for clarity and style")
        .build())
    .mapTaskFactory((chapter, agent) -> Task.builder()
        .description("Edit " + chapter)
        .expectedOutput("Edited " + chapter)
        .agent(agent)
        .build())
    .reduceAgent(Agent.builder()
        .role("Senior Editor")
        .goal("Ensure consistency across all chapters")
        .build())
    .reduceTaskFactory((results, agent) -> Task.builder()
        .description("Review all edited chapters for consistency")
        .expectedOutput("Final editorial notes")
        .agent(agent)
        .build())
    .chatLanguageModel(model)
    .build()
    .run();
Enter fullscreen mode Exit fullscreen mode

There's also an adaptive mode where an LLM decides how to partition the work at runtime.

5. Production Tooling Built In

This is the biggest difference. CrewAI gives you agents and tasks. AgentEnsemble gives you agents, tasks, and the production infrastructure:

Feature CrewAI AgentEnsemble
Rate limiting External .rateLimit() builder method
Cost tracking External .costConfiguration() builder method
Micrometer metrics N/A .meterRegistry() builder method
Structured traces External .traceExporter() builder method
Human review gates External .reviewHandler() + .reviewPolicy()
Input/output guardrails External .inputGuardrail() / .outputGuardrail() on agents
Capture mode for testing External .captureMode(CaptureMode.FULL)
Live development dashboard External .devtools(Devtools.enabled())
Parallel error strategies N/A ParallelErrorStrategy.CONTINUE_ON_ERROR

None of these require additional libraries or custom code. They're all builder methods on the same API.

6. Native JVM Deployment

No Python runtime, no virtual environment, no pip dependencies, no requirements.txt, no separate container image. Your agent code is just Java code that ships in the same JAR as the rest of your application:

// build.gradle.kts
dependencies {
    implementation("net.agentensemble:agentensemble-core:2.3.0")
}
Enter fullscreen mode Exit fullscreen mode

It works with your existing:

  • Gradle or Maven build
  • JUnit test suite
  • Spring Boot application (framework integration module available)
  • Docker image
  • Kubernetes deployment
  • CI/CD pipeline

No polyglot overhead.

What You Give Up

To be fair, the Python ecosystem has advantages:

  • Larger community: More tutorials, Stack Overflow answers, and blog posts for Python agent frameworks.
  • Faster prototyping: Python's dynamic typing can be faster for throwaway experiments.
  • Broader LLM library support: Some cutting-edge LLM features land in Python first.

AgentEnsemble mitigates the third point by building on LangChain4j, which supports OpenAI, Anthropic, Google, Ollama, Azure OpenAI, Amazon Bedrock, and many others. But there may be niche providers or features that aren't available yet.

Making the Decision

If your team is already writing Python, CrewAI and friends are fine choices. Use what fits your stack.

But if your production backend is Java, adding a Python layer for agent orchestration introduces:

  • A second language to maintain
  • A second dependency tree to audit
  • A second runtime to monitor
  • A REST boundary with serialization overhead
  • A deployment topology change

AgentEnsemble eliminates all of that. Same language, same build, same runtime, same monitoring. Your agent system is just another module in your existing application.

Get started:


AgentEnsemble is MIT-licensed and available on GitHub.

Top comments (0)