AI Agents with Java (for Raymond Rhine) (Alex Soto Bueno, Markus Eisele, Mario Fusco)(Z-Library)
Author: Alex Soto Bueno, Markus Eisele, Mario Fusco
Java
AI agents are reshaping how enterprise software is conceived, built, and operated. Yet many teams remain stuck at the prototype stage--bolting isolated AI services onto Java backends and encountering security, latency, and integration headaches. This book gives experienced developers and architects a clear, practical path to designing autonomous systems directly in the JVM, bringing scale, reliability, observability, and governance to agentic AI.
📄 File Format:
PDF
💾 File Size:
3.0 MB
16
Views
0
Downloads
0.00
Total Donations
📄 Text Preview (First 20 pages)
ℹ️
Registered users can read the full content for free
Register as a Gaohf Library member to read the complete e-book online for free and enjoy a better reading experience.
📄 Page
1
(This page has no text content)
📄 Page
2
AI Agents with Java Designing Autonomous Systems for the Enterprise With Early Release ebooks, you get books in their earliest form—the authors’ raw and unedited content as they write—so you can take advantage of these technologies long before the official release of these titles. by Alex Soto Bueno, Markus Eisele, and Mario Fusco
📄 Page
3
AI Agents with Java by Alex Soto Bueno, Markus Eisele, and Mario Fusco Copyright © 2027 Alex Soto Bueno, Markus Eisele and Mario Fusco. All rights reserved. Published by O’Reilly Media, Inc., 141 Stony Circle, Suite 195, Santa Rosa, CA 95401. O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions are also available for most titles (https://oreilly.com). For more information, contact our corporate/institutional sales department: 800-998-9938 or corporate@oreilly.com . Acquisitions Editor: Simina Calin Development Editor: Melissa Potter Production Editor: Elizabeth Faerm Copyeditor: TO COME Proofreader: TO COME Indexer: TO COME Cover Designer: Susan Brown Cover Illustrator: Monica Kamsvaag Interior Designer: David Futato Interior Illustrator: Kate Dullea March 2027: First Edition
📄 Page
4
Revision History for the Early Release 2026-03-23: First Release See https://oreilly.com/catalog/errata.csp?isbn=9798341666801 for release details. The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. AI Agents with Java, the cover image, and related trade dress are trademarks of O’Reilly Media, Inc. The views expressed in this work are those of the authors and do not represent the publisher’s views. While the publisher and the authors have used good faith efforts to ensure that the information and instructions contained in this work are accurate, the publisher and the authors disclaim all responsibility for errors or omissions, including without limitation responsibility for damages resulting from the use of or reliance on this work. Use of the information and instructions contained in this work is at your own risk. If any code samples or other technology this work contains or describes is subject to open source licenses or the intellectual property rights of others, it is your responsibility to ensure that your use thereof complies with such licenses and/or rights. 979-8-341-66676-4 [LSI]
📄 Page
5
Brief Table of Contents (Not Yet Final) Chapter 1: From AI Features to Autonomous Agents (unavailable) Chapter 2: The Java Advantage for AI Agents (unavailable) Chapter 3: Agent Anatomy: Components and Lifecycle (unavailable) Chapter 4: Frameworks and Tools Overview (unavailable) Chapter 5: Multi-Agent Coordination Patterns (available) Chapter 6: Implementing Reasoning Loops (unavailable) Chapter 7: Memory and Context Management (unavailable) Chapter 8: RAG as Intelligent World Model (available) Chapter 9: Tool Integration (unavailable) Chapter 10: Agent Communication Protocols (unavailable) Chapter 11: Building Agent Orchestration Systems (unavailable) Chapter 12: Distributed Agent Architectures (unavailable)
📄 Page
6
Chapter 1. Multi-Agent Coordination Patterns A NOTE FOR EARLY RELEASE READERS With Early Release ebooks, you get books in their earliest form—the author’s raw and unedited content as they write—so you can take advantage of these technologies long before the official release of these titles. This will be the 5th chapter of the final book. Please note that the GitHub repo will be made active later on. If you’d like to be actively involved in reviewing and commenting on this draft, please reach out to the editor at mpotter@oreilly.com. In many applications, a single agent may not suffice to perform complex tasks, and in this case it is necessary to employ multiple agents working together to achieve a common goal. However, coordinating the actions and interactions of multiple agents can be challenging and requires to be carefully designed. In fact using a single, general purpose, agent, where possible, is often preferable, because of its simpler design, ease of maintenance and debugging and higher efficiency due to the lack of any communication and coordination overhead. That said, there are several advanced domains where a single agent may not be enough to handle the complexity and variety of the tasks involved and multi-agent setups can really shine. In fact, multi-agent systems, not only can mimic the way of working of a whole team of human experts, but also provide many other advantages. First of all, small specialized agents can offer better performance and efficiency for specific tasks at a fraction of the cost of a general purpose agent employing a much bigger large language
📄 Page
7
model. Additionally, multiple agents can work in parallel at the same time, reducing latency for complex tasks. Moreover, they can improve the reliability of the overall system, by checking the work of each other and catching and correcting their mistakes and hallucinations. Finally, they can be more flexible and adaptable to changing environments or requirements For these reasons, multi-agent systems are often employed in situations where tasks are complex, dynamic, or require specialized knowledge or skills. They are also advisable when verification and safety matter and multiple independent opinions are needed to ensure correctness and reliability of the results, or when the achievement of the desired outcome involves many independent tasks that can be naturally parallelized, like in large scale data processing or multistep planning. Multi-agent systems can even be a winning choice in interactive environments, like negotiation or markets simulations, where each agent can model a different perspective and provide a diverse point of view. In these scenarios, a multi-agent system can be more than the sum of its parts, achieving outcomes that individual agents cannot accomplish alone. In these cases, a well-designed coordination among the agents participating in the same system is crucial to ensure that they work together harmoniously and efficiently. This chapter clarifies the advantages of the multi-agent approach, explores various coordination patterns that enable effective agents collaboration and analyzes in details their pros and cons, demonstrating how to use them in practice. Workflow Agents Orchestration The most straightforward way to coordinate multiple agents is orchestrating them in a predetermined workflow. In this approach, each agent is assigned a specific role or task in the overall process, and the interactions among them are structured in a fixed way. Despite its simplicity and lower degree of flexibility, this architecture can be very effective in many realistic scenarios, especially when the tasks involved are well-defined and can be easily decomposed into smaller sub-tasks. These deterministic workflows
📄 Page
8
also have the advantage of mimicking the way human teams often work together, with each member having a specific role and responsibility. This section discusses the four basic agentic workflow patterns, sequencing, reflection, parallelization and routing, with their respective fields of application, also demonstrating how they can be composed together to cover more complex scenarios. We will illustrate these with practical examples, using the features offered by the LangChain4j agentic module, that you can integrate with your Java applications by adding the following dependency to your pom.xml file. <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-agentic</artifactId> </dependency> Sequencing The first and simplest agentic workflow pattern is sequencing, where agents are arranged in a linear sequence, with each agent performing its task and then passing the result to the next agent in line. For this reason, sequencing is also called prompt chaining, since the output of an agent becomes the prompt of the subsequent agent in the sequence. This pattern is particularly useful when tasks need to be performed in a specific order, with each step depending on the output of the previous one. To give a practical example of sequencing, let’s consider a scenario where a job applicant needs to generate his CV starting from his personal history and then adapt this CV to a specific job description. In this case, we can use two agents in sequence, the first one specialized in CV generation and the second one in CV adaptation. The first agent takes the applicant’s personal history as input and generates a CV, which is then passed to the second agent along with the job description to produce a tailored CV for that specific job. The code to define these two agents is as follows. public interface CvGenerator { @UserMessage(""" Here is information on my life and professional trajectory
📄 Page
9
that you should turn into a clean and complete CV. Do not invent facts and do not leave out skills or experiences. This CV will later be cleaned up, for now, make sure it is complete. Return only the CV, no other text. My life story: {{lifeStory}} """) @Agent("Generates a clean CV based on user-provided information") String generateCv(@V("lifeStory") String userInfo); } public interface CvTailor { @SystemMessage(""" Here is a CV that needs tailoring to a specific job description, or other instruction. You can make the CV look good to meet the requirements, but don't invent facts. You can drop irrelevant things if it makes the CV better suited to the instructions. The goal is that the applicant gets an interview and can then live up to the CV. The master CV: {{masterCv}} """) @UserMessage(""" Here are the instructions for tailoring the CV: {{instructions}} """) @Agent("Tailors a CV according to specific instructions") String tailorCv(@V("masterCv") String masterCv, @V("instructions") String instructions); } In LangChain4j an agent can be defined with an interface having a single method annotated with @Agent. It is a good practice, even though not strictly required, to also provide with that annotation a short description of the agent’s purpose. Moreover, the input arguments of these agents are annotated with a variable name allowing the LangChain4j agentic framework to automatically feed them with inputs taken from a shared memory area, as we will clarify later in this section.
📄 Page
10
At this point we have two independent agents, but we want to make them working together. Not only that: in reality our final goal is to create and expose a single agents-based service that can take the life story and the job description as inputs and produce the tailored CV as output. To this purpose, let’s create a third interface with an agentic method having exactly this signature. public interface SequenceCvGenerator { @Agent(""" Generates a CV based on user-provided information and tailored to instructions """) String generateTailoredCv(@V("lifeStory") String lifeStory, @V("instructions") String instructions); } Having defined this interface, the LangChain4j agentic framework allow us to create a service implementing it by creating the instances of the two agents developed before and chaining them in a sequence. CvGenerator cvGenerator = AgenticServices .agentBuilder(CvGenerator.class) .chatModel(CHAT_MODEL) .outputKey("masterCv") .build(); CvTailor cvTailor = AgenticServices .agentBuilder(CvTailor.class) .chatModel(CHAT_MODEL) .outputKey("tailoredCv") .build(); SequenceCvGenerator sequenceCvGenerator = AgenticServices .sequenceBuilder(SequenceCvGenerator.class) .subAgents(cvGenerator, cvTailor) .outputKey("tailoredCv") .build(); Here the SequenceCvGenerator agent is built by defining it as the sequence of the two sub-agents, cvGenerator and cvTailor. The first agent takes in input the life story of the candidate and generates in output a cv based on it, binding it to a variable named masterCv. If you prefer also
📄 Page
11
this output name can be declared within the @Agent annotation, instead of being programmatically specified. The second agent takes in input this masterCv and the instructions to make it a better fit to a given job description, bound to the variable instructions, and produces in output another CV that is better tailored to the job. As anticipated before, the LangChain4j agentic framework automatically manages a shared memory area, called AgenticScope where it stores the inputs and outputs of all the agents involved in the same agentic system. The input arguments of each agent are automatically filled with the values stored in this memory area, based on the variable names specified with the @V annotation. In this way, the output of one agent can be seamlessly passed as input to another agent without any additional glue code. The AgenticScope is automatically created when the top agent of the agentic system is invoked and programmatically provided to other agents through callbacks when necessary. In fact its purpose is not only to provide a common memory area where all the agents involved in the same agentic system can read and write data, but also to keep track of other relevant information like the sequence of invocations of all agents with their responses. This allows, when necessary, to provide each agent with a more comprehensive context, including all the actions taken by all agents collaborating to achieve a common goal instead of having a memory that is only local to a single agent. The SequenceCvGenerator agent can be now invoked as a single service, providing it with the life story of the candidate and the instruction to properly taylor it to a specific job description. String lifeStory = StringLoader.loadFromResource("/documents/user_life_story.txt"); String instructions = "Adapt the CV to the job description below." + StringLoader.loadFromResource("/documents/job_description_backend.txt" ); String tailoredCv = sequenceCvGenerator.generateTailoredCv(lifeStory,
📄 Page
12
instructions); System.out.println(tailoredCv); This is a first step towards the creation of a more complex multi-agent system, where each agent is specialized in a specific task and the overall process is orchestrated in a well-defined sequence. In this case, sequencing allows to break down a complex task into smaller, manageable sub-tasks, each handled by a dedicated agent, resulting in a more efficient and effective solution. Nevertheless, the outcome of the cvTailor agent, used to edit a CV and make it a better fit to a specific job description, is still not totally satisfying and could be significatively improved through multiple rewriting iterations. This improvement can be achieved through the reflection pattern, putting the rewriting agent in a loop, as it will be discussed in the next section. Reflection loop Reflection is another commonly used agentic pattern. It allows an agent to iteratively improve its output by reviewing and refining it based on feedback or additional information. This pattern is particularly useful when the task at hand is complex or subjective, and the initial output may not fully meet the desired quality or requirements. To illustrate the reflection pattern, let’s extend the previous example of CV generation and tailoring. In this case, we can create a reflection loop replacing the simple CvTailor agent. To do so the first necessary thing is an object modelling the feedback generated by a reviewer agent, that another agent could use to improve the final CV. public class CvReview { @Description("Score from 0 to 1 how likely you would interview this candidate") public double score; @Description(""" Feedback on the CV, what is good, what needs improvement, what skills are missing, what red flags, ...
📄 Page
13
""") public String feedback; } This CvReview class has two fields, a numerical score from 0 to 1 indicating how likely the reviewer agent would be to invite the candidate for an interview, and a textual feedback providing more detailed comments on the strengths and weaknesses of the CV. Now we can define the CvReviewer agent, taking in input the generated CV and the job description and producing in output a CvReview object: public interface CvReviewer { @SystemMessage(""" You are the hiring manager for this job: {{jobDescription}} Your review applicant CVs and need to decide who of the many applicants you invite for an on-site interview. You give each CV a score and feedback, both the good and the bad things. You can ignore things like missing address and placeholders. """) @UserMessage(""" Review this CV: {{cv}} """) @Agent(""" Reviews a CV according to specific instructions, gives feedback and a score """) CvReview reviewCv(@V("cv") String cv, @V("jobDescription") String jobDescription); } together with a second agent using the review generated by the first one to improve the CV according to the feedback received: public interface ScoredCvTailor { @SystemMessage(""" Here is a CV that needs tailoring to a specific job description,
📄 Page
14
feedback or other instruction. You can make the CV look good to meet the requirements, but don't invent facts. You can drop irrelevant things if it makes the CV better suited to the instructions. The goal is that the applicant gets an interview and can then live up to the CV. The current CV: {{cv}} """) @UserMessage(""" Here are the instructions and feedback for tailoring the CV: (Again, do not invent facts that are not part of the original CV. If the applicant is not suitable, highlight his existing features that match most closely, but do not make up facts) The review: {{cvReview}} """) @Agent("Tailors a CV according to specific instructions") String tailorCv(@V("cv") String cv, @V("cvReview") CvReview cvReview); } The LangChain4j agentic framework allows to easily create a reflection loop by combining these two agents together and repeating the process for a specified number of iterations or until a certain condition is met. In this case, we can create a cdReviewerLoop agent that uses the CvReviewer and ScoredCvTailor agents in a loop to iteratively improve the CV based on the feedback received. UntypedAgent cdReviewerLoop = AgenticServices.loopBuilder() .subAgents(cvReviewer, scoredCvTailor) .outputKey("cv") // this is the final output that we want to observe .exitCondition(agenticScope -> { CvReview review = (CvReview) agenticScope.readState("cvReview"); System.out.println("Checking exit condition with score = " + review.score); return review.score > 0.8; })
📄 Page
15
.maxIterations(3) .build(); Here the reflection loop is configured to stop either when the score given by the reviewer agent exceeds 0.8 or when a maximum of 3 iterations have been performed. The exitCondition method takes a lambda function that receives the current AgenticScope as input and returns a boolean indicating whether the loop should continue or stop. In this case, it reads the latest cvReview from the shared memory and checks its score. You can also note that we haven’t defined a specific interface for this reflection loop agent, since it is not strictly necessary. In fact, the LangChain4j agentic framework allows to create untyped agents that can be invoked through a generic invoke method, providing the input arguments as a map of variable names and values. This is particularly useful for agents that are not meant to be exposed as standalone services, but rather used as building blocks within a larger agentic system like in this case. In fact, we are going to use this reflection loop as a sub-agent of a sequence, that also includes the initial CV generation step. LoopCvReviewer reviewedCvGenerator = AgenticServices .sequenceBuilder(LoopCvReviewer.class) .subAgents(cvGenerator, cdReviewerLoop) .outputKey("cv") .build(); We created this latest agent with a proper interface, since it is the top level one of our agentic system meant to be exposed directly to the user with the following method: public interface LoopCvReviewer { @Agent(""" Generates a CV based on user-provided information and tailored to instructions """) String reviewCv(@V("lifeStory") String lifeStory, @V("jobDescription") String jobDescription); }
📄 Page
16
At this point we are ready to put this agentic system at work, by providing it with the life story of the candidate and the job description to which the CV should be tailored. String lifeStory = loadFromResource("/documents/user_life_story.txt"); String jobDescription = loadFromResource("/documents/job_description_backend.txt"); String tailoredCv = reviewedCvGenerator.reviewCv(lifeStory, jobDescription); System.out.println(tailoredCv); So far we have demonstrated the first two basic agentic workflow patterns, sequencing and reflection, showing how they can be combined to help a job applicant to generate a CV based on his life story and tailored for a specific job description. In the remaining part of this section we will explore the other two basic patterns, parallelization and conditional routing, putting ourselves on the opposite part of the barricade and playing the role of the hiring company, that needs to review multiple CVs and select the most promising candidates for an interview. Parallelization Parallelization is another important agentic workflow pattern that allows multiple agents to work simultaneously on different independent parts of a task or on different tasks altogether. To illustrate how multiple agents can work in parallel, let’s consider a scenario where a hiring company needs to review multiple CVs and select the most promising candidates for an interview. Each CV has to be reviewed independently by different people with different roles in the company, like the hiring manager, a technical member of the team and an HR expert. These reviews can be performed in parallel, by multiple agents specialized in each of these roles. public interface HrCvReviewer { @Agent(name = "hrReviewer", description = """ Reviews a CV to check if candidate fits HR requirements, gives feedback and a
📄 Page
17
score """) @SystemMessage(""" You are working for HR and review CVs to fill a position with these requirements: {{hrRequirements}} You give each CV a score and feedback (both the good and the bad things). You can ignore things like missing address and placeholders. """) @UserMessage(""" Review this CV: {{candidateCv}} with accompanying phone interview notes: {{phoneInterviewNotes}} """) CvReview reviewCv(@V("candidateCv") String cv, @V("phoneInterviewNotes") String phoneInterviewNotes, @V("hrRequirements") String hrRequirements); } public interface ManagerCvReviewer { @Agent(name = "managerReviewer", description = """ Reviews a CV based on a job description, gives feedback and a score """) @SystemMessage(""" You are the hiring manager for this job: {{jobDescription}} Your review applicant CVs and need to decide who of the many applicants you invite for an on-site interview. You give each CV a score and feedback (both the good and the bad things). You can ignore things like missing address and placeholders. """) @UserMessage("Review this CV: {{candidateCv}}") CvReview reviewCv(@V("candidateCv") String cv, @V("jobDescription") String jobDescription); } public interface TeamMemberCvReviewer { @Agent(name = "teamMemberReviewer", description = """
📄 Page
18
Reviews a CV to see if candidate fits in the team, gives feedback and a score """) @SystemMessage(""" You work in a team with motivated, self-driven colleagues and a lot of freedom. Your team values collaboration, responsibility and pragmatism. Your review applicant CVs and need to decide how well this person will fit in your team. You give each CV a score and feedback (both the good and the bad things). You can ignore things like missing address and placeholders. """) @UserMessage("Review this CV: {{candidateCv}}") CvReview reviewCv(@V("candidateCv") String cv); } All these agents have a similar structure: they take in input the CV to be reviewed and some additional context information, like the job description or the HR requirements, and produce in output a CvReview object containing the score and feedback. Now we can create a parallelCvReviewer agent that uses these three agents in parallel to review multiple CVs at the same time and then aggregate their reviews into a single comprehensive review. HrCvReviewer hrCvReviewer = AgenticServices .agentBuilder(HrCvReviewer.class) .chatModel(CHAT_MODEL).outputKey("hrReview").build(); ManagerCvReviewer managerCvReviewer = AgenticServices .agentBuilder(ManagerCvReviewer.class) .chatModel(CHAT_MODEL).outputKey("managerReview").build(); TeamMemberCvReviewer teamMemberCvReviewer = AgenticServices .agentBuilder(TeamMemberCvReviewer.class) .chatModel(CHAT_MODEL).outputKey("teamMemberReview").build(); UntypedAgent parallelCvReviewer = AgenticServices.parallelBuilder() .subAgents(hrCvReviewer, managerCvReviewer, teamMemberCvReviewer) .outputKey("fullCvReview")
📄 Page
19
.output(agenticScope -> { // read the outputs of each reviewer from the agentic scope CvReview hrReview = (CvReview) agenticScope .readState("hrReview"); CvReview managerReview = (CvReview) agenticScope .readState("managerReview"); CvReview teamMemberReview = (CvReview) agenticScope .readState("teamMemberReview"); // return a bundled review with averaged // score (or any other aggregation you want here) String feedback = String.join("\n", "HR Review: " + hrReview.feedback, "Manager Review: " + managerReview.feedback, "Team Member Review: " + teamMemberReview.feedback ); double avgScore = (hrReview.score + managerReview.score + teamMemberReview.score) / 3.0; return new CvReview(avgScore, feedback); }) .build(); It is now possible to put this parallelCvReviewer agent at work, by providing it with a CV to be reviewed along with the necessary context information for each reviewer agent. String candidateCv = loadFromResource("/documents/tailored_cv.txt"); String jobDescription = loadFromResource("/documents/job_description_backend.txt"); String hrRequirements = loadFromResource("/documents/hr_requirements.txt"); String phoneInterviewNotes = loadFromResource("/documents/phone_interview_notes.txt"); Map<String, Object> arguments = Map.of( "candidateCv", candidateCv, "jobDescription", jobDescription ,"hrRequirements", hrRequirements ,"phoneInterviewNotes", phoneInterviewNotes ); var review = parallelCvReviewer.invoke(arguments);
📄 Page
20
Having defined and tested the parallelCvReviewer agent, we can now integrate it into a larger agentic system that takes different action based on the outcome of the review, as it will be explained in the next section. Conditional routing The forth building block of agentic workflows is conditional routing, which allows to direct the flow of tasks among multiple agents based on specific conditions or criteria. This pattern is particularly useful when different agents are specialized in handling different types of tasks or when the outcome of one agent’s work determines which agent should be invoked next. To illustrate the conditional routing pattern, let’s extend the previous example of CV review and selection. In this case, we can create a cvSelectionRouter agent that uses the parallelCvReviewer agent to review a CV and then decides what to do next based on the score given by the reviewers. If the score is above a certain threshold, the candidate is selected for an interview, that can be organized by another agent; public interface InterviewOrganizer { @Agent("Organizes on-site interviews with applicants") @SystemMessage(""" You organize on-site meetings by sending a calendar invite to all implied employees for a 3h interview in one week from the current date, in the morning. You also invite the candidate with a congratulatory email and interview details. Lastly, you update the application status to 'invited on- site'. """) @UserMessage(""" Organize the interview for this job: {{jobDescription}} With this candidate: {{candidateContact}} """) String organize(@V("candidateContact") String candidateContact, @V("jobDescription") String jobDescription); }
The above is a preview of the first 20 pages. Register to read the complete e-book.
Recommended for You
Loading recommended books...
Failed to load, please try again later