DevBcn 2026

From Prompts to Workflows: Building Agentic AI with LangChain4j

51:26 · 16 Jun 2026 – 17 Jun 2026 · YouTube

About this talk

This talk covers the development of agentic AI using Java, with a focus on its structure and implementation through tools like LangChain for J. The speaker, Alex Soto, first introduces the capabilities for developing AI applications and the differences in producing agents. He discusses the advantages of using Java over Python, like energy efficiency in production settings, and highlights the use of LangChain for creating complex prompting, managing memory, and implementing Remote Procedure Calls. Soto emphasizes the role of the Model Context Protocol (MCP) for improving interconnectivity between tools and standards for agent development, allowing for seamless function calls across various programming languages. The session also dives into various agentic patterns, which enable structured workflows among multiple agents, making it easier to handle tasks in a modular fashion, showcasing practical applications like a travel agency using agent coordination.

Full transcript

Thank you very much for coming to this session about, you know, from prompts to workflows or, you know, how to build agentic AI with with Java. I would say that in this session we're going to it's like two parts. One which is like an introduction of, you know, of let's say all the features that we can use when we want to develop an AI application. And the

second part which is more how to use it for developing agents, which is, you know, kind of a different thing. It's At the end it's the same. It's just a matter of putting a name. But well, yeah, we'll see that the differences, okay? So, my name is Alex Soto. My Twitter is Alex Soto B and my email is [email protected]. And yes, you can send me an email,

no problem. For the reason the mail is here, so feel free to send it. And then then there is this QR code. Basically, I'm the co-author of all these books and some of them can be downloaded for free. You can scan the the QR code. You go to developers.redhat.com. You register, it's free, and then you can download it the PDF. I think that for now you can

only download it two. Which one is about Ansible. So, if you want to get certified of Ansible I'm sure that here there is not a lot of operations people, but in case you can do it. And the second thing is the applied AI for enterprise Java development, which covers some of the parts of this talk. So, I know that in the AI space most of the people

are using Python, right? Yeah, that's true. But well, if you love the planet yeah, you maybe you should start considering using Java instead of Python. You see here the how you are saving energy when you are using Java. Yeah, of course, if you want to keep your mind, you know, peace out of the Python ecosystem. I don't know if you've been working with Python a lot, but

it's super funny when you have something in Python that works with 3.23, doesn't work with 3.22, but it works with 3.21. And it's like, "What?" I mean, that it's just a minor version, or what's happened with 22? Okay. That's good. And also, at the very end, big corporates uses Java, right? Because we need observability. We need ORMs. We need resiliency. We need also to know how to

put these into production and leave for a long time, right? So, I think that Java and the G JVM works really well with this. So, if you have zero Python application in production, that remains the right number, okay? That's all. I mean, that I I I I I thought that I hate JavaScript, but no, I hate more Python. Not the language, but the ecosystem, right? The language

is great, but the ecosystem, yeah, okay. That's Then, let us start with the first talk, right? Which is like, what is LangChain for J? Or what we can do with LangChain for J? Basically, maybe when we want to interact with a model, we'll say, "Hey, yeah." At the end, the models are, you know, are inferenced with a REST API. So, we could just access this REST API.

We could just create a REST client, and so on. Yeah, you can do it, but as this B, you know that this B is missing something. Uh and this is what you're going to miss if you implement on your own the REST API to access a model, right? For example, complex prompting, or what happens if the mappings of this REST API changes. So, for that reason, what

we've got is LangChain for J, which basically it's I like to say that like it's like Hibernate, but for models, okay? Basically, it lets you, first of all, create complex promptings, system messages, user messages, uh templates, okay? It lets you also create templates, so it's good. Also, manages all the memory stuff. We know that models are stateless. So, we need to provide some context, some memory of

our conversations, right? So, this is automatically managed by LangChain4j. It also implement let's implement rack systems. And when I mean rack systems, I'm not saying, "Oh, yes, I am just ingesting these documents and then I just, you know, put it there in a vector store." I'm just talking about complex chunking strategies or complex rack operations like like multi-layer vectorizing, okay? So, it's works really well with this.

Also implements the function calling or also MCPs. We'll see in a moment. Probably you know that models has no dynamism, right? We can We cannot ask them, "What is the weather today in Barcelona?" We cannot do that. So, how we can make models do or could ask or give us a response for this question. Also, LangChain4j acts, as I said, as an Hibernate. So, you can have

different models, right? And then maybe you're using OpenAI and then at some point you change to Amazon Bedrock. You only need to change one dependency and done, right? So, it's it's kind of magic. And also lets you implement agents, which we're going to see by the end of the session. So, how we going to start using LangChain4j? Well, in this case, I'm just showing you how to

use LangChain4j in Quarkus, but it doesn't matter because LangChain4j is a or it works with vanilla Java. So, you can do a void main method and just start using LangChain4j. You can just use it LangChain4j with a Spring Boot. It also has the integration In this case, I'm going to show you with Quarkus, okay? Then, how you how it works? Uh well, basically you add this dependency,

the LangChain4j OpenAI. If you want to use Bedrock, you are you add the Quarkus LangChain4j Bedrock, okay? And this basically switches the model. In the case of using LangChain4j alone, just changes the coordinates. That's you need to configure the application. Basically, you say in this case the API key for OpenAI, and you can just, for example, say, "Hey, log me all the requests. Log me all the

responses." Well, uh the temperature and so on. Then, we only need to create an interface. And that's kind of, you know, what I love about LangChain4j, because I do not need to write any class. Just an that [snorts] I said, "Hey, and you see here I'm saying register as a AI service." I'm saying, "This is an AI service." Okay? Then, uh I want to add some kind

of system message, so like an initial instructions and context for for the model. And then, you see here that I'm setting here the user message. And you see that this user message is a prompt, and well, it's kind of more complex, because it's not just text. It has some, you know, you see here some placeholders. And then, automatically, LangChain4j makes the matches between the arguments and the

prompt. So, automatically, it replaces at runtime the "You're professional poet." Okay, nothing. Then, "Write a poem about" and then it will replace the topic with the given topic and the lines with the given lines, and send this to the model. Just these lines. And we are interacting with a model. Of course, we can use the low-level API, because what I showed you before is the high-level API.

It's like, okay, you create an interface, no access to the low-level stuff. Uh then, how you can do that? Basically, with inject chat model. So, I'm just injecting the low-level object. Um this basically lets you, for example, define the user message not with an annotation, but with a class. You say, "Hey, this is a user message from text and from an image." So, you can you know,

you you know, you can mix the contents, and then say, "model .chat." You set the user message and done. And then you can do response.ai message. You get the AI message. You get the response and so on and so forth. For example, you can get uh things like if a tool was called or not and things like that, okay? So, this is the low level. That's great,

right? Because it's easy to either use the high level or the low Now, why this looks good? Basically, because everything is typed. You see that it's Java code. There is no, you know, no strings, nothing. It's testable. The prompt is separated. You can just have, you know, uh annotated or within a string coming from a file and it basically injects very well into your code. Basically, using

in the case of a Spring Boot with AutoWire, in the case let's say Jakarta EE with CDI. So, yeah, it works really well. The problem is that you might say, "Hey, yes, that works really good if you've got a string and it returns on a string, right?" So, for a chatbot, I'm sending a text, a free text, and it returned me a free text. Great. But what

happened if I want to make the response make machine readable? So, basically, you say, "Hey, I don't know. Here is a text. The invoice probably contains uh you know, an an an IBAN number and a name and a date of the transaction and so on, right?" Things like that. And I want to parse this text and get back. Okay? If I got a string, this is not

human readable. It's it's not machine readable, sorry. It's human readable. So, what you can do is things like that. You can instead of return on a string, you can return a Java object. In this case, an invoice data. So, you create it um invoice extractor. Notice that here I'm just showing you how to do it with LangChain for J vanilla. So, with no Spring Boot, no Quarkus,

nothing. So, you'll see that the idea is the same, but only changes the annotations. So, you create an interface, a system message extractor, a structured invoice data, and in this case, I'm just not putting any user message, so automatically LangChain4j uses the argument as a user message. And then it returns a class. Then, what's going to do LangChain4j? Easy. This is the class. I'm going to use

JSON marshalling uh stuff for transforming the text that is coming from model, which basically it's a JSON file, to an object. So, it manages all these kind of things automatically. So, then, of course, you need to do the chat model. Remember that with Quarkus, we just injected the chat model and it worked. Magically, Quarkus takes all the things and configures the objects. In case of vanilla, of

course, there is uh container CDI container, so you need to do it manually. You do chat model open AI chat model.builder, you put the open API key, the model built, you get it the model, and then you just do AI services.create, and you set the interface, you put the model, and you get it this running. So, in Quarkus, you've got all these, let's code or boilerplate code

replaced by the framework. If not, you can do it in this way. It's all all good. In a Spring Boot, it's exactly the same, okay? It's it's as in Quarkus. It just does everything automatically. So, now, basically, with this approach, all the uh stuff that are coming from a model, it's validatable, persistable, and testable, right? Because it's a Java object, so we send in a string, we

get it a Java object, which automatically gets all these fields populated, and we can write tests. So, good thing. Problem is hallucinations. What's happened when the model is say, "Hey, this is the, you know, this is the text that I want you to parse and fill all this class." And yeah, of course, model will try to do its best. Basically, doing its best by taking the name

of the fields. Which is not always maybe possible or not not descriptive a lot a lot. Then, you can just do a uh JSON schema enforcement. So, you can create programmatically um a JSON schema, right? In this case, you say, "Hey, just there's a root element which is, you know, which is a JSON schema with name, age, it's required name and age, and so on." So, you

define all the schema. And then, in the user message, you've got the user message, and you create a chat request parameters where you set, as you can see here, the schema. So, this instance goes inside the params, and then you do, "Okay, chat user message chat request parameters." In this way, what you are saying is sending the request to the model, but you're also saying to the

model, "Hey model, the response is JSON it returns a complex object, but this object needs to follow this schema." So, let's say that you help a lot the model to not start hallucinating, and you get more predictable results. Okay, this is how it works all this um let's say standard prompting with Now, as I said before, a model has no way to interact with external wall per

se. I mean, if you ask to the model, "What is the weather in Barcelona?" the model will say, "I have no idea." Or it will hallucinate and tell you something that something like, "Oh, yes, usually it's sunny." Yeah, probably can do things like that, or yeah. But, what the model providers, let's call it in this way, did was creating this tool calling. Basically, is a way that

have the models to do remote calls. And as I said, it's a remote call. It's like the old RPC that we've been doing for a long long time like well, the old ones CORBA and RMI. If you are not that old, maybe EJBs and now then REST, SOAP and so on. It's this. There is nothing new function calling. What is important to understand is that the LLM

is the client and your application is the server. Which means that the LLM only decides to invoke or not the function. But it's your application who's going to run the function. It's a remote call. how it works? Well, it works in this way. Basically, we've got a lot of um tools. In this case, we've got one which is like get weather. And when you send the request

to the model, you send the system message, you send user message and also all the signature and descriptions and of all the tools or all the methods that are available. So, you send all these to the model. Then the model will start, you know, getting his thing and say, "Oh, the the you know, the person is asking me about I don't know the weather. And I don't

know nothing about weather. But I know that there is a signal there is a method that has a description that say, 'Hey, if you need to know the weather, invoke me.' And I'll say, 'No, I know that, you know, it has some arguments of that type and so on.' So, what I'm going to do is "Hey, application, can you invoke me the get weather method?" Then the

the application will just run the get weather method, get the result and send back the result to the LLM. Just keep in mind that this consumes a lot of tokens. Right? First, because at the first request you're sending a lot of extra information, so the more tools you've got, the more information you're sending to And the second thing is that now, when you're sending the result, you're

not only sending the result, you're sending, "Hey, for the original request, I invoked this method and this method has these parameters and so on and so forth." So, you're just kind of duplicating, right? From invoking from method to method the amount of tokens that you need. Um yeah, that's how it works. Then, how you can define this is super easy. You'll go to your AI service and

say, "This AI service can use a tool, which is the weather tool. This is just a class, you'll see now. Or, if you only want to invoke it in a method, so suppose that you've got several methods in this AI interface, you can just use the annotation toolbox, you pass the class, and that's all." And this class is something like this. It's a class that has a

method or multiple methods with or annotated with tool. You put here the description, search for the weather of the given city. You In this case, you know, you're I'm defining also the an an argument. That's all. So, with just this line of code, adding to the context of the chat um the chat interface or the chat method uh this method as well. So, every time that I

run the or invoke the chat method, the original chat method, this uh this one, I'm just also sending the signature and the description of this tool of this uh annotated methods. So, you'll say, "Okay, great. Now, I can just start, you know, annotating all my code with tools." I mean, if I need that the model can invoke that methods, but there are some problems. First of all,

is that the your application and the functions must be in the same language. You're seeing here that okay, my iOS service is in Java and my tool is also in Java. Of course, because you need to put it everything here together. Uh then, yeah, it's great, works, but there is no interoperability. You see that okay, if I what's happening if my uh application is in Java and

I've got a tool that is implemented in another language? Let's say go, because I don't want to say the other semi languages or let's call it whatever. This is the first thing. The second thing is that they are hard to reuse. What's happening if my get weather tool must be reused by several applications? Yeah, if you are in Java, that's that's fine. I can create a JAR

file. I add the JAR file into the class path, done. But what's happening if the other application is not in Java? Or Yeah, okay, you know, it's like okay, now I need to manage multiple versions of the same. So, it's kind of difficult. What we've got then is the MCP, the model context protocol standard, which is an open standard that basically lets the AI models connect to

tools in an standard way. So, no more, let's say, uh problems of hey, the MCP the tool is in Java. I need my application to be in Java. No, because now there is a standard. Since there is a standard, your tool could be in go and my application application can be in Java. Or I can reuse this function to all my projects. Then, you see here that

now there are like another piece here. One is the MCP per se and another one is the And it works like this. I've got my application and I've got the MCP server. Basically, the MCP server is the the the bridge between the MCP protocol and my real application. In this case, I put it an example with we GitHub MCP server. Basically, this GitHub MCP server is a

bridge between MCP protocol and REST API. Okay, so it's this kind of bridge. Now, my application maybe say, "Hey, when I just start my application, you register all the MCP clients or all the MCP tools and say, "Oh, look, I've implemented a MCP client which is I don't know what. Give me all the tools that you are representing, right?" And then ask to the GitHub MCP server

and then the MCP server says, "Hey, look, I've got all these tools registered." For example, I'll get repository three, RTC coming, issue read, search issue, and so on and so forth. Okay. Now, I've got all these tools. I am going to send a request to the user message and say, "Can you fix the issue number 67?" And I'm going to send this to the LLM. Remember that

since we've got all these tools, what I'm sending to the model is my user message plus all these methods. Now, the LLM will say, "Hmm, okay. You asked me to fix the issue number 67 with all the list of these tools, but I need to know about what is this issue number 67 because I have no idea." So, what I'm going to say is, "Hey, application, remember

that now we it's an it's an RPC application, so my model is doing nothing. The model is just saying to my application, invoke something." Which basically is invoke the issue read with argument number 67. So, my application will use the MCP client to connect to the GitHub MCP server saying, "Hey, can you read the issue number 67?" Okay, then the MCP server will connect to GitHub. The

GitHub will return the description to the GitHub MCP server and the MCP server will get back me the result saying, "Okay, there's a null pointer exception at this blah blah blah." Okay, all the information. Great. Now I've got everything. I put it together everything and I send to the LLM. So you see that in this case, it's the same as a function of calling. But the big

difference is that I've got this MCP bridge in the middle. Means that I can just start a GitHub MCP server once and this instance can be reused by all my applications in the same way. And the GitHub MCP server can be in Go and my application can be in how do you develop an MCP server? Of course, you know that and there is a lot of MCP

servers out there like this GitHub, the Terraform MCP server. I mean, there's a lot of of them, Kubernetes MCP server, right? There's a lot of them there, but you can also implement your own MCP server. Basically, in this case I'm just creating an MCP server using the SSA. So I just add the transport protocol. If it's protocol, then I just to change the the suffix from SSA

to um SSTDA. Now, I only need to do this. Just a with a CDI bean. This is uh of course using um using Jakarta EE in fact, okay? you define a tool, the same annotation that we've seen before, saying, "Hey, I want that you expose this method to us an MCP server method." Then I can even describe the arguments. If I do not describe it, that's fine.

The LLM will try its best, but you can always, you know, use the tool arc to describe. And that's all the things that you need to do. Nothing else. So you can just create an MCP server in Java with just three lines. Just this. one dependency, one class, one method, Of course, depending on the logic, it will be more classes or not, but that's the scaffold thing.

Now, just a remark for Quarkus, by default, Quarkus doesn't do a Uber jar, okay? So, when you are starting using an MCP service with with a special with a state you want to run them easily with Java minus jar, you put it the jar and done, right? This is what you want. So, in the application the properties, I recommend you put it this Uber jar, and then

Quarkus will create Uber jar instead of this a special Quarkus packaging. On the client side, um it's not that hard. Add the dependency. Again, this is the Quarkus dependency, but if you want to use vanilla LangChain4j, there is the you know, there is the io.langchain4j, and it's LangChain4j.mcp, And now, you use this other annotation, say, "Hey, this is the AI service." Now, instead of using the MC

the toolbox annotation, remember? Now, I'm using the MCP toolbox with a name. The name is in this case Zodiac. Now, where do you define this? Here. In the application the properties, you say, "Hey, there is the Quarkus LangChain4j.mcp.zodiac, the transport is HTTP, and the location is this host." Done. With just this, now every time that I invoke the ask me method, I will go to, you know,

I will send all the MCP server methods to the model, and if the model requires, it will just go there, take the method, ask to the my application to invoke it. And then, of course, LangChain4j will connect to my MCP make the request, get the response, and so on. So, it's I know that there is a round trip around, but you are getting an great abstraction. First

talk is about skills because okay, now I've got my prompts. I know how make my model invoke my methods, but well, you know, this is a guitar. Yeah. I need a tool. And the LLM is a tool. But, here is the key point. This tool is not used in the same way by Brian May or Billy Armstrong, right? They have different musical styles, different ways of playing

the guitar. They have different skills. And that's exactly what you can do with a skills. Basically, what you do with the skills saying the model how it should behave. And basically, and a skill is a folder with instructions, most of them in markdown. mandatory file is one named a skill.md where you just create a markdown file with kind of instructions, and then you can also add extra

folders like, for example, where you put assets, scripts, wherever you put their code, wherever there. Then, in the skill.md file, you put the metadata of your skill and all the content. uh invoke this a script, and the script it knows that it's in this folder, this helps a lot of reusability because you've got a skill.md file or a folder that you can reuse to different projects, for

example, using I don't know you skills jar project, which is a project that lets you reuse skills using package it as a jar file. So, it means that your the skills are a Maven dependency. So, you could just put it as a Maven dependency and then the skill is You can share it, of course, and they're writing natural language because it's And one thing that you need

to be aware is that you append the skill as a context, which means that your prompt gets bigger. So, more tokens, but there are a lot of studies that says that skills uses less tokens than uh MCP tools. So, always tries to use a skills if you can. If not, then go to tools. And it was like this. Okay, this is the you know, the folder, my

skill number one. I can have a lot of a skills. Then my skill.md has the metadata. For example, here is there is a name and the description. And then there is the you know, a description of things. For example, for example, to process an order, call this method, call this other method. Only if reservation succeeds, call this method. Finally, call this other method. Even if it fails,

call bomb before returning the error. So, you see that I'm just kind of defining how I want that the model behave under, for processing an order. Now, how this works? Okay, I can have a lot of a skills. Basically, um when I just do a request to the model, I'm just adding the system message, the user message, and the metadata. So, this part, okay? The the name

and the description. Then I send all this to the model. And then the model will just analyze all my user message, all my description of the skills, and then at some point will say, "Hey, hm, I'm interested in this a skill. So, can you activate this a skill?" Then my application will go there, find the skill, append to the context all the markdown file with everything, and

then finally send it to the model. So, now the model will have your user the plus all the instructions of the skill. Um what is important here is that this is the metadata and the name should be folder with the same name, right? So, if the name is poem poem writing, then inside skills, which is the default uh folder, needs to be into a poem writing. If

not, it's not going to work. Uh as I said, by default you put all the skills into a skill folder, but this is configurable. you only need to do this. We provide this system message provide supplier. You add the skill system message provider. You just annotate your AI service with this. In the case of um of the vanilla LangChain4j, it's similar, not using the register AI service,

but using another annotation or method, and that's all. all the magic happens. As I said, by the skills directory is in is in the skills, but if you want to customize, you can just use this um property to customize it. Again, easy. Just one line. You provide all these skills things, and it's done. Um yeah, of course, you also need to add this uh dependency. In the

case of uh again, in the case of LangChain4j is uh langchain4j.skills. In the case of Quarkus, then it's quarkus.langchain4j.skills. In the case of Spring Boot, I don't know exactly what it's going to be. Spring Boot LangChain4j.skills. another thing that we need when we are implementing AI applications is that our models are uh stateless. They have no memory. And when we want to do a chatbot, we need

kind of memory, because if I ask to the model, "Hey, can you check the weather in Barcelona?" And said, "Okay, the weather is this one." And then you can say, "Okay, and what flights are available to go there?" This there it means to Barcelona, but the model has no memory, so has no clue of what is there. So, basically, what we want to do with uh models,

especially if we do a chatbot, is that they need to memory. And how we can do that? Okay, with LangChain4j super easy. Basically, it's I send the request and I get it a response. That's good. I send another request, but the important thing here is that I'm sending the new request appending the previous request and response. And then I will get another response, which is the green

one. Then I'm just sending another request. So, in this request, I will have the original request, the response of I got it, you know, in the first interaction, the response of the second interaction, and so on. That's how it works. Appending context all the time to the message. How to do that? with Quarkus, you you don't need to do anything at all. Just all the requests are

always stored. So, you always have memory. But, and that's the key point, it's only enabled per request. It means that if I send a request, when I get the response, the memory is uh lost. So, you got memory if you have several interactions in the same method, in the same request, then it works. If uh memory's wiped out. Okay? That's how it works. And it stores all

these messages into a default in-memory store. Uh you can also set the max amount of messages that you want to store. And then, of course, you can just not use a memory store, but you know, another store, for example, using Redis, right? So, if we add this dependency, now my messages will be stored in Redis instead of the in-memory. This works really well when if you want

to stay, right? Or you survive to any kind of breaks. Then, of course, the problem is that, I need memory that lives across requests." Then what you can do is application scope. And you see here that I'm using this memory ID annotation. Basically, if this is application scope, it means that all the requests uses the same instance. So, I need to verify that this is my memory

and not the memory of another request. So, you're using a memory ID, which you can use set here as string ID, which could be your user ID. Basically, internally, it's like a map, right? So, if you need to have multiple users using the memory, you need to have a a map to um define which are the memory fits of the users. Then, one of the great things

is that if you're using Quarkus WebSocket next, you only need to set this as session scope. And you don't need to do memory ID, because automatically Quarkus uses the WebSocket session ID as a memory, right? So, you don't need to do anything at all. So, we are great, right? Now, we've built all this B, we've got the memory, we've got the prompts, we've got the skills, we've

got the MCP tools. So, we've got everything. Now, when we're using all these everything or most of the parts of this B, we've got an agent. So, if you're just doing prompting, okay, you're doing prompting. You You You do not have an agent, but you're just prompting, which is great. But when you have all using most of these things, now we can consider that you are using

an agent. And this is when we all start agents. So, agents. uh, what happened here is that Okay, I've got, you know, my application, which is a monolith. I'm using some promptings and so on. At the end, I start using memory, MCP tools, and so on. So, now I've got one agent. The problem is that usually my application will end up having different agents. For example, if

I'm a travel agency, I will have an agent for booking flights or finding flights. Another for car rentals, another for hotel bookings, and so on. So, probably you will end up having different agents, not just one agent. And remember that one agent is prompts plus memory plus, you know, tool calling tools, and so on. And you will need some kind of, first of all, coordination, right? In

the case of travel agency, I will need to book or find flights, flying hotels, and so on. Also, all these agents will share common data. If I'm, again, with the travel agency example, um, if I want to fly to Amsterdam from one day to another day, I need the dates and the place, right? In all these agents. Because I need to find a book or I need

to book a hotel in Amsterdam, and I need to find a flight from Barcelona to Amsterdam. So, I need this information. um, how we can do this thing? Okay? We can do it with patterns, with agentic patterns. Basically, agentic patterns um, group of yeah, as the same of schemas, right? That let us make calls between agents. For example, sequence, parallel, loop, conditionals, routers, and so on. So,

this is a example where we you can have the sequence and the parallel, uh, patterns working, right? I've got one agent, which is the flight. First, I need to find the flight because depending on when is the flight, I need to book the hotel, right? So, first, I find the flight, then I find the hotel, then in parallel I'm going to find some activities that I can

do in my destination, and all these agents shares the agent scope. Basically, it's all this information. And this is what I called like a micro leaf approach. Basically, is your application has different agents defined it inside this application. So, there is no remote calls between agents. So, it's all together, but the good thing is that they modularize it. Okay, so you've got your agent modularize it in

the same How this works? Well, it's super easy. Add the dependency. Again, LangChain for J Agentic or Quarkus LangChain for J Agentic. Now, uh registry AI service or anything, you use the agent annotation. You can also use the AI services annotation that we've seen before, user message, MCTP uh toolbox, and so on. And then in the agent, you set the output key. Because at the end you're

running several agents at some point maybe if you are do running them in parallels, you need to know the output of each of the agent. So, you set here, okay, this is the movies, and then there is a method that says find movie. This is the mood. I use the the user message, and so on. So, this is an agent that I'm going to use for finding

a movie. Now, this is just an interface that I can inject again as I did with the AI service. Movie expert, movie expert agent, I can call the find movie, and it will work. Now, the important thing here is that now I can just start creating um let's say um workflows. For example, in this case, I'm just creating a parallel agent, as you can see here at

the beginning, parallel agent, which the output is plans, and I have two sub agents, the food expert and the movie expert that we've seen before. Now, here it is is the method plan. This is where I want to execute this in parallel, and this is how I can compose the output of both of the of the agents, okay? So, you see that now I'm just getting like

a super agent, if you will, that are going to use in parallel two other This is parallel. You can also do it in sequence, so you use sequence. And then now here it's going to first use the creative creative writer and then the audience editor. I can just do a loop So, I can say, "Hey, just uh start looping through style scorer and style editor. The output

should be in the story, and the exit condition is if the score So, the score is something that is coming from the agents are greater or equal to 0.8, or if I do five iterations, okay? So, when I do five iterations or the score is okay, I will stop looping through all these agents. Uh also, I can do a conditional agent. So, I've got two agents, medical

expert, technical expert, which I need to call. Okay, then I've got the activation activation condition annotation, where I basically say, "Hey, activate the medical um expert agent when the category is medical." Now, basically I will have another which basically will classify the input message or the user message. And this case is another agent that see that um returns agent re-category, like here. Uh sorry, right here, okay?

So, basically I'm just saying, "Hey, check, find the request, send it to the activation condition. You will have one activation condition per agent, and then finally, it will just, you know, find the agent and invoke only this agent." And as you can see here, also we will have an expert router agent, which basically first categorize, right? So, find which is the agent and calls the exploration which

basically will choose the exact agent. So, you can see that you have start composing patterns of um of agents. of course, this is was a microlith application um approach, but what happen if I have several services? Right? Several agents. And maybe some of these I want to be shared across different other agents or maybe the agent is deployed in another part of the of the organization or

I don't know, Google agent. And I want to use this How I can use this other agent if it's remote? Basically, we've got the A2A protocol, the agent to agent protocol. Again, it's in a standard which is enterprise ready and it works really well. It works like MCP but for agents. And what is one thing that is important with A2A is that you create agent cards. Basically,

you are describing what is the purpose of the agent. And this works really well when you want to make agents do things autonomously. Because then they are able to just read the agent card and understand the purpose of this agent. So, basically, it works something like this. I've got in this case uh microlith application that requires two models, the scorer and the styler, but then I've got

another model which is the writer, which is a model that understands or knows how to write um books. But it's remote. So, my application will do a remote call to the writer. The writer agent will return me a result using the A2A protocol and then I will start this looping of a scorer or style till the book is correct. But the key point here is that the

writer is out of my application. It's in it's usually in the in the HTTP remote site, and the scorer and the styler are running. Not running, I mean that are part of my application. How to do this? Super easy, again. This is the creative writer. Remember the creative writer is the is the remote one. So, not create So, how I connect to this remote agent? Okay, I

just create a creative writer. In this case, not this that I'm showing you the programmatic declaration approach. I'm not using the annotations. And then, I just said, "Okay, agent agentic services.dot.a2aBuilder, I set the URL." Launch In For J does the rest for you. You just say, "Hey, you see that the definition is exactly Sorry, it's I mean that this is a creative writer. It's just an interface.

Here, it's exactly the same. But, the only change I'm doing is that when I'm creating the creative writer, in the first point, there is no agent uh So, in in the first point, I'm just defining the agent as local. In the second part, I'm defining the remote. I am The only change is that I'm using the a2aBuilder um method. another great another um pattern that I love

about agents is the supervisor agent. Basically, is I've got an agent that knows or they have a list of agents, right? And then, the supervisor says, "Hey, planner, the user asked me this question." And you've got all these agents, and see that these agents can be remote or not. Uh I know that these agents does this, this, this, this, and that. Just plan what is the best

execution flow to resolve the question that the user has. everything is autonomous. There is no workflow. It's These are the agents. This is the question. Find the solution. It will just start doing planning things, finding the solutions, and finally gets you the response. And to do the supervisor agent, you only need to do the supervisor um annotation. Again, interface, supervisor agent. I put the list of agents.

Here in this case, I put a here the a method, and that's all. And now, let me example that shows all these things. Here is the link. You can run it for your own. Uh I've not uh started yet this um project. Just You see that it's super easy. The first thing is a front end. I only I'm going to do npm run dev. So, I'm just

starting the front end in JavaScript. Now, here I've got my back end. In the case of back end, I'm going to run it using Maven wrapper dev. Uh basically, I'm uh starting the back end now. Uh this back end uses PostgreSQL databases and so on. But, one of the great things about Oops. Okay. Well, I If you want, I can start it again. I mean, that is

just starting Quarkus dev mode. Basically, I'm starting the um Quarkus dev mode, which starts a PostgreSQL database for me in my Podman instance and so on. So, now it's Here it's running. And then here, I've got the MCP [clears throat] server. I'm going to start it again. I done something wrong. Basically, I've got front end, back end, and MCP server that running. Okay, this MCP server could

be running somewhere. Now, let me start here the application here local host. Okay, so basically it's a Galaxium Travels, okay? Here I've I've already logged in with Alice. I can just go here and say my bookings and these are my bookings. I can, you know, find flights, I can book flights, and so on. But one of the important things here for this talk is this chatbot. So

I say, "Hey, I'm I'm I'm just here to help you." And I can ask, "Can you uh give me a list of my bookings?" Now, while it works, I'll just tell you that I'm using the supervisor pattern. So I just have a list of agents and MCP tools. MCP tools for accessing the database for finding my bookings, or as you'll see to get the local time of

each of the planet, right? So I just have a lot of things there and the supervisor pattern will do, you know, find my question, find all the stuff that I give to him and say, "Yeah, I'm just going to start running this, this, this, and that." And finally, it returns my my bookings, okay? If you uh in the logs, it's you see that it's saying it's just

logging all the all the invocations. So you can see you see here that it says, "As an invocation, I'm in invoking this agent, then I'm invoking this tool, then you see here this the SQL query, and then finally, I can say, 'Okay, now this is done.'" And since the supervisor say, "Hey, you asked me for bookings and now these are bookings, we are done." Okay, this is

how it works and it's really well. Now I've got here Nope, sorry. Uh let me I've got it a complex prompt here. It's like, uh can you, hope that it works, uh provide me at arrival for my booking number 11. Hope that it works. Because yeah, this is a supervisor pattern that does whatever thinks it's best. Uh let's see if it works. >> [snorts] >> Okay, now

it's doing yes. You see here that the MCP server was called. And finally it says the local time at arrival for your booking number 11 is this. Martian time. Okay, remember that my booking number 11 you cannot see here. Not here. Okay, but I won't show you here. It's to Mars. The booking 11 is to Mars. So, I'm not done anything. You can see in the code.

Uh this is not wait. Okay, I've just created this supervisor agent with a booking agent and a time agent. Basically, an agent that knows how to transform times from Earth to any solar system planet. Another one that is for bookings. You can see here the booking agent. Okay, this it's this. This is the time agent. Which is this other one. And then the supervisor just magically said,

"Oh, you're asking me for a time for booking number 11. And yeah, the time is in Earth, but you are the destination is in Mars. Okay, what I need to do first is finding the details of booking number 11. And then when I find the booking number 11, I need to get the destination and then invoke the other agent for transforming the time from Mars." Right? So,

it's really really impressive. Now, we're almost getting out of time. So, I will say that Quarkus for business logic works really well. Of course, if you're using Spring Boot, yes, Launch and Forge A works with the Spring Boot. In fact, with with you know, with micronaut, with all the um Java technologies, it works really well because it's at the end it's LangChain for J is framework agnostic.

Uh you can use A2A, you can use MCP, right, for remote um communications. Um then, of course, don't get crazy and I'm going to start using an agent a supervisor agent. No, start with prompting, then, you know, structured output, and then tools, rack, agents, and then finally move all this knowledge to your enterprise. Of course, AI is just the cherry on top of your sundae, so you

still need infrastructure. Your application is still needs to be predictable, okay? And then, yeah, this is the QR code for the slides. So, if you want to, you know, get these slides uh you know, in the later, you just scan this and then you've got also the QR for the books. And if you want to run the demo as I as you've got the link also there

and you can run it um locally. In fact, the the demo is super bigger than this, but for the AI part, it's just this this thing. So, thank you very much for coming. >> [applause] >> We are out of time. I mean, that if you have any question, I would say come to here. I'm happy to to help you. And yeah, I mean, I will be around,

so feel free to approach me.

From event

DevBcn 2026

16 Jun 2026 – 17 Jun 2026

All event videos
Back to Watch