Skip to main content

03 - JADE framework: implementing the first agent

JADE checked on 2026-11-13: a historical site at 4.6.0, a fork at 4.5.3, no official Maven Central coordinates. Java is a sketch, behaviour is simulated.

What do you think about this article?

At a Glance

JADE checked on 2026-11-13: a historical site at 4.6.0, a fork at 4.5.3, no official Maven Central coordinates. Java is a sketch, behaviour is simulated.

You want several agents to talk on the same platform, and the name that comes up everywhere is JADE, the Java agent framework that conforms to the FIPA specifications. Before writing a line, it is worth asking an unglamorous question: what state is it in today? A library to which you cannot add a dependency with one line of pom.xml changes the way you use it, and in this case the answer may not be the one you expect.

The short answer, checked on 2026-11-13: the project's historical website lists as its latest version 4.6.0 of 7 December 2022; there is a community fork on GitLab, stuck at version 4.5.3 and with no commit on the main branch after 7 February 2024; and JADE is not on Maven Central with official coordinates. For this reason the Java code in this article is a sketch to adapt, not executed: I wrote it from the official Javadoc, I never compiled it. The behaviour the code describes, instead, you demonstrate with a Python simulator of your own, run twice with the same output.

Before installing: what state JADE is in

Each line of the list is a fact with its source, all consulted on 2026-11-13. Where the source does not answer from this machine, the line says so.

  • Historical website (jade.tilab.com): from this machine the page requests timed out. I read a copy preserved by Internet Archive on 2 December 2025, which reports "Jade 4.6.0 latest version (07/12/2022)" and the LGPL licence (version 2) with Telecom Italia as the rights holder.
  • Community fork (gitlab.com/jade-project/jade): the README says it is a fork of JADE 4.5, born because the group that developed it can no longer continue. The pom.xml declares version 4.5.3, the GitLab API reports as the last commit of the master branch the one of 2024-02-07, and three tags (2026.02, m4.5.3, 4.5.3).
  • Distribution of the fork: the fork's download page explains how to add it to a Maven project through JitPack, with version master-SNAPSHOT. I did not try JitPack.
  • Maven Central: searches for com.tilab.jade, eu.mas and com.gitlab.jade-project find nothing; the search for the class jade.core.Agent finds only net.sf.ingenias:jade, a third-party republication that stops at 4.3 (September 2015).
  • Compatibility with the JDK you use: the historical website declares Java 5 as the minimum requirement; I have not verified that the code runs on a recent JDK, because I did not launch it.

The picture that emerges is not "abandoned", but not "maintained" either in the sense meant for a living library: there is a recent version on the historical website, a fork that preserves 4.5 and a single declared channel for those who use Maven, which goes through a third-party service and which I did not try. It is a project decision to take with these facts in hand, not with those you find in an old tutorial.

The ideas of JADE in five objects

The Javadoc you cite is that of version 4.5.0, published on the fork's website. There are five objects, and they are the ones needed to read the code that follows.

ObjectWhat it does (from the 4.5.0 Javadoc)
jade.core.AgentThe class to extend. setup() and takeDown() are empty placeholders for start-up and clean-up, addBehaviour(Behaviour) adds a behaviour, receive(MessageTemplate) returns the first message that matches the template or null.
CyclicBehaviour and TickerBehaviourThe first is an atomic behaviour to be executed for ever (its done() always returns false); the second calls onTick() at intervals of a given number of milliseconds.
Behaviour.block()It is not a blocking call. When action() returns, the behaviour moves into a queue of blocked ones and returns among the active ones when a message arrives or it is restarted with restart().
jade.lang.acl.ACLMessageThe message: constants such as REQUEST and INFORM for the performative, setContent, addReceiver and createReply(), which creates the reply by setting recipient, language, ontology, protocol and conversation identifier.
DFService, DFAgentDescription, ServiceDescriptionThe Directory Facilitator, the yellow-pages service: register publishes an agent, search returns the agents that offer a service, deregister removes it.

The objects are few because the platform does the rest: the programmer writes classes that extend Agent and behaviours with an action() method, and JADE decides when to call them. The object that matters most for understanding performance is block(): without it, a behaviour that waits for a message is called back continuously. You will see it measured in the simulator shortly.

The Java sketch: an agent that answers and one that asks

The first block is the agent that offers an "echo" service. In setup() it builds a DFAgentDescription with its own identifier, adds to it a ServiceDescription of type echo-service and registers it with DFService.register; if the registration fails, it calls doDelete() and terminates. The cyclic behaviour asks receive with a template that accepts only REQUEST messages: if the answer is null it calls block() and exits, otherwise it builds with createReply() an INFORM reply with the content repeated and sends it. Remove block(), and the behaviour keeps repeating receive to no purpose. takeDown() deregisters the agent from the DF. The identifiers are Italian (AgenteEco is the echo agent, soloRichieste is "only requests", risposta is "reply"); the strings and comments are in English, and the type of the service is "echo-service".

import jade.core.Agent;
import jade.core.behaviours.CyclicBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;

public class AgenteEco extends Agent {

    @Override
    protected void setup() {
        DFAgentDescription dfd = new DFAgentDescription();
        dfd.setName(getAID());
        ServiceDescription sd = new ServiceDescription();
        sd.setType("echo-service");
        sd.setName("echo-" + getLocalName());
        dfd.addServices(sd);
        try {
            DFService.register(this, dfd);
        } catch (FIPAException e) {
            doDelete();
            return;
        }

        addBehaviour(new CyclicBehaviour(this) {
            private final MessageTemplate soloRichieste =
                    MessageTemplate.MatchPerformative(ACLMessage.REQUEST);

            @Override
            public void action() {
                ACLMessage msg = myAgent.receive(soloRichieste);
                if (msg == null) {
                    block();
                    return;
                }
                ACLMessage risposta = msg.createReply();
                risposta.setPerformative(ACLMessage.INFORM);
                risposta.setContent("echo:" + msg.getContent());
                myAgent.send(risposta);
            }
        });
    }

    @Override
    protected void takeDown() {
        try {
            DFService.deregister(this);
        } catch (FIPAException e) {
            // the DF may already be gone: nothing else to clean up
        }
    }
}

The second block is the agent that asks. It uses a TickerBehaviour with a period of 5000 milliseconds; at each onTick() it searches the DF for an agent of type echo-service with DFService.search. If it finds none it returns without doing anything, and retries at the next tick; if it finds one, it builds a REQUEST message, adds the recipient with addReceiver and sends it. This is the reason the caller does not know the name of the other agent: it knows only the type of service.

import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.TickerBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;

public class AgenteChiamante extends Agent {

    @Override
    protected void setup() {
        addBehaviour(new TickerBehaviour(this, 5000) {   // onTick() every 5000 ms
            private int n = 0;

            @Override
            protected void onTick() {
                DFAgentDescription modello = new DFAgentDescription();
                ServiceDescription sd = new ServiceDescription();
                sd.setType("echo-service");
                modello.addServices(sd);
                try {
                    DFAgentDescription[] trovati = DFService.search(myAgent, modello);
                    if (trovati.length == 0) {
                        return;                           // no echo registered: retry at the next tick
                    }
                    n++;
                    ACLMessage richiesta = new ACLMessage(ACLMessage.REQUEST);
                    richiesta.addReceiver(trovati[0].getName());
                    richiesta.setContent("ping " + n);
                    myAgent.send(richiesta);
                } catch (FIPAException e) {
                    // search failed: it will show at the next tick
                }
            }
        });
    }
}

I state the limit plainly: these two files are a sketch to adapt, not executed. For every class, method and constant I checked name and signature against the 4.5.0 Javadoc, but I never compiled them, because the library is not on Maven Central and I neither downloaded nor compiled the fork. The platform start-up class is jade.Boot, which the Javadoc describes as what starts JADE by reading the command-line arguments, and the options are listed with Boot.printUsage(): I do not report them because I did not try them. I did not even start a main container, so I have not measured which network ports it opens.

The same behaviour in a Python simulator

What can be demonstrated here is the logic: behaviours that the system calls in turn, receive that does not block, block() that suspends until a message arrives, a directory of services. The simulator is not JADE and reproduces nothing else of it: no network, no containers, no FIPA encoding of messages, no threads. The method names follow those in the Javadoc (receive, block, send, crea_risposta for createReply). The first block contains the messages, the behaviours and the platform with its cycle.

"""Simulator of an agent platform: it is NOT JADE, it reproduces its execution model
(cooperative behaviours, non-blocking receive(), block(), Directory Facilitator)."""

REQUEST, INFORM = "REQUEST", "INFORM"


class Messaggio:
    def __init__(self, performativa, mittente, destinatario, contenuto):
        self.performativa, self.mittente = performativa, mittente
        self.destinatario, self.contenuto = destinatario, contenuto

    def crea_risposta(self):
        # like ACLMessage.createReply(): the recipient becomes the sender
        return Messaggio(None, self.destinatario, self.mittente, None)


class Comportamento:
    def __init__(self, agente):
        self.agente, self.bloccato, self.chiamate = agente, False, 0

    def block(self):
        self.bloccato = True        # NOT a blocking call: it takes effect when action() has finished


class Piattaforma:
    def __init__(self):
        self.agenti, self.df, self.t, self.log = {}, {}, 0, []

    def aggiungi(self, agente):
        self.agenti[agente.nome] = agente
        agente.piattaforma = self
        agente.setup()

    def invia(self, msg):
        dest = self.agenti[msg.destinatario]
        dest.coda.append(msg)
        for c in dest.comportamenti:     # the arrival of a message wakes up the blocked ones
            c.bloccato = False
        self.log.append(f"t={self.t:<2} {msg.mittente} -> {msg.destinatario} "
                        f"{msg.performativa} {msg.contenuto!r}")

    def esegui(self, istanti):
        for self.t in range(1, istanti + 1):
            for ag in list(self.agenti.values()):      # round-robin, creation order
                for c in list(ag.comportamenti):
                    if not c.bloccato:
                        c.chiamate += 1
                        c.action()

The esegui (run) cycle goes in turn over each agent, in the order in which they were created, and over each of its non-blocked behaviours: it calls action() and counts the call. The important rule is in invia (send): when a message arrives, all the blocked behaviours of the recipient agent are unblocked, as the Javadoc of block() says for the arrival of a message. If you remove that line, a blocked behaviour would never restart.

The second block contains the base agent and the echo agent. The Ricevi (receive) class inside setup is the cyclic behaviour: it asks for a REQUEST message, and if there is none it blocks only when con_block is true; if there is one, it creates the INFORM reply with the content repeated and sends it. The con_block parameter ("with block") serves only the experiment: with False the behaviour polls to no purpose at every step.

class Agente:
    def __init__(self, nome):
        self.nome, self.coda, self.comportamenti, self.piattaforma = nome, [], [], None

    def setup(self):
        pass

    def receive(self, performativa):               # non-blocking, as in JADE
        for m in self.coda:
            if m.performativa == performativa:
                self.coda.remove(m)
                return m
        return None

    def send(self, msg):
        self.piattaforma.invia(msg)


class Eco(Agente):
    def __init__(self, nome, con_block=True):
        super().__init__(nome)
        self.con_block = con_block

    def setup(self):
        self.piattaforma.df.setdefault("echo-service", []).append(self.nome)   # register in the DF
        agente, con_block = self, self.con_block

        class Ricevi(Comportamento):               # the CyclicBehaviour: never finishes
            def action(self):
                msg = agente.receive(REQUEST)
                if msg is None:
                    if con_block:
                        self.block()
                    return
                r = msg.crea_risposta()
                r.performativa, r.contenuto = INFORM, "echo:" + msg.contenuto
                agente.send(r)
        self.ricevi = Ricevi(self)
        self.comportamenti.append(self.ricevi)

The third block contains the agent that asks and the experiment. Chiamante (caller) has an interval behaviour: every 5 instants it looks in the directory for an echo-service service and sends a request. The function prova (trial) puts an echo and a caller on the platform, runs 30 instants and returns the number of calls to the echo's action(). The last assert is the check made by hand: the echo runs before the caller, so a request sent at instant t is served at t+1; the calls are 1 initial plus 2 for each of the 5 requests served (one that replies, one that blocks), and the sixth request, sent at instant 30, does not make it in time to be served.

class Chiamante(Agente):
    def __init__(self, nome, periodo):
        super().__init__(nome)
        self.periodo, self.n = periodo, 0

    def setup(self):
        agente = self

        class Ticker(Comportamento):               # the TickerBehaviour: onTick every 'periodo'
            def action(self):
                t = agente.piattaforma.t
                if t % agente.periodo == 0:
                    eco = agente.piattaforma.df["echo-service"][0]   # search in the DF
                    agente.n += 1
                    agente.send(Messaggio(REQUEST, agente.nome, eco, f"ping {agente.n}"))
                agente.receive(INFORM)             # consume the replies
        self.comportamenti.append(Ticker(self))


def prova(con_block):
    p = Piattaforma()
    p.aggiungi(Eco("echo", con_block))
    p.aggiungi(Chiamante("caller", 5))
    p.esegui(30)
    return p, p.agenti["echo"].ricevi.chiamate


if __name__ == "__main__":
    p, con = prova(True)
    print("\n".join(p.log[:4]), "...", sep="\n")
    _, senza = prova(False)
    print(f"messages exchanged: {len(p.log)}")
    print(f"action() calls of Eco in 30 instants: with block() = {con}, without block() = {senza}")
    # Hand check: Eco runs before the caller, so the request sent at
    # time t is served at t+1. Calls: 1 initial + 2 for each of the 5 requests
    # served (the one that replies, the one that blocks). The sixth, at t=30, is not served.
    assert con == 1 + 2 * 5 and senza == 30 and len(p.log) == 6 + 5
    print("hand checks: ok")

# Output (two identical runs):
# t=5  caller -> echo REQUEST 'ping 1'
# t=6  echo -> caller INFORM 'echo:ping 1'
# t=10 caller -> echo REQUEST 'ping 2'
# t=11 echo -> caller INFORM 'echo:ping 2'
# ...
# messages exchanged: 11
# action() calls of Eco in 30 instants: with block() = 11, without block() = 30
# hand checks: ok

Read the two numbers. With block() the echo executes action() 11 times in 30 instants, without block() 30: one per instant, to no purpose for the most part. The messages exchanged are 11, that is 6 requests and 5 replies, because the last request stays in the queue. The difference is not an optimisation: with block() the echo spends work only when there is something to do, whereas without it the work grows with time and not with messages. This is the reason why in the Java sketch the msg == null branch calls block().

What the simulator does not tell you

The simulator's numbers are correct for the simulator. They say nothing about JADE on these questions.

  • Real timings: the "calls of action()" are a count, not a cost in milliseconds or in CPU.
  • The behaviour of several containers on the network and of messages between different machines.
  • The encoding of messages and compliance with the FIPA specifications, which my Messaggio (message) does not touch.
  • Compatibility with your version of Java, which requires really compiling the library or the fork.

All four items are things that only a run with the real library can show, and it is the reason the Java code remains a sketch. If you decide to adopt JADE, the first step is not writing agents: it is downloading the library from the chosen channel, compiling the two files in this article and seeing whether the output resembles that of the simulator. If it does not, the library's counts.

Frequently asked questions

Is JADE still maintained?

It depends on what you call JADE. The historical website lists 4.6.0 of 7 December 2022; the fork on GitLab is stuck at 4.5.3 and its last commit on the main branch is from 7 February 2024. I have not measured the activity of the community beyond these data.

Can JADE be downloaded from Maven Central?

Not with official coordinates, according to the searches of 2026-11-13: no results for com.tilab.jade, eu.mas or com.gitlab.jade-project. There is a third-party republication, net.sf.ingenias:jade, stuck at 4.3 from 2015. The fork indicates JitPack as the channel.

Why was the Java code not executed?

Because the library is not on Maven Central with official coordinates, and I neither downloaded nor compiled the fork from GitLab or from JitPack. I did not even start a main container, so I have not measured which ports it opens nor whether the code works. The two Java files are a sketch to adapt, checked for names against the Javadoc and never compiled.

What you can do now

You can assess the state of a library before adopting it, with sources and a date, and you can say what that state implies for how you download it. You can read JADE's behaviour model (setup, cyclic behaviour, non-blocking receive, block(), Directory Facilitator) on a Java sketch and see its meaning in a simulator that you ran twice with the same result. The previous article gave structure to an agent's decision, the next one goes into FIPA-ACL messages, and the series index lists the rest.

Sources

  • jade-project/jade (GitLab) - README of the fork, licence, version 4.5.3 in the pom.xml, last commit of the master branch (2024-02-07) and tags, read through the public GitLab API, consulted on 2026-11-13
  • Download Jade (the fork's website) - indications for JitPack and availability of 4.5, consulted on 2026-11-13
  • jade.core.Agent (JADE 4.5.0 Javadoc) - setup, takeDown, addBehaviour, receive, send, consulted on 2026-11-13; the Javadocs of Behaviour, CyclicBehaviour, TickerBehaviour, ACLMessage, MessageTemplate, DFService, DFAgentDescription, ServiceDescription and Boot are under the same address, /API/jade/...
  • jade.tilab.com, Internet Archive copy of 2025-12-02 - version 4.6.0 of 07/12/2022, LGPL licence and Java 5 requirement; the original site did not answer from this machine, consulted on 2026-11-13
  • Search for the class jade.core.Agent on Maven Central - the only republications found, by third parties; the searches for group com.tilab.jade, eu.mas and com.gitlab.jade-project gave zero results, consulted on 2026-11-13
  • The simulator's outputs come from python3 -I mini_piattaforma.py on the listing of the article (three blocks, contiguous portions of the same file), run twice with identical output on 2026-11-13; the two Java files were neither compiled nor run. The English strings were translated in the Python listing and it was run again twice with the same numbers.

Commenti

Caricamento commenti...

Sign in to comment

Discuss this article in the community

Join the Telegram group to discuss with other developers, ask questions and share your experiences.

Did you like this article?

Explore other content on the blog or discover my projects