This tutorial is the third part of the “Setting up an infrastructure for Instant Messaging” article series. In my previous tutorials, I showed you how to setup the Openfire IM Server and how to configure the Spark client in order to connect to that server. In this tutorial, I will show you how to add XMPP messaging capabilities to your own application.
I will use the Smack library, an Open Source XMPP (Jabber) client library for instant messaging and presence. Smack is a pure Java library and can be embedded into your applications to create anything from a full XMPP client to simple XMPP integrations.
XMPP is a simple XML protocol. Under the hood, XML messages are transmitted from the server to the client and vice versa. However, you don't have to deal with those low level details, unless you want to. Smack hides all these via a well designed API. You can take a first hint of how the API works by reading the article “Instant Messaging in Java Made Easy: The Smack API”. I believe that you are familiar with the concept of instant messaging in general, so we are ready to write some code.
First download the library from the Ignite Realtime site. Since Smack is written in Java, it is cross-platform. The current version is 3.1.0 and I downloaded the tar.gz version (direct download here). After you extract the bundle file, you will find the library JARs along with the corresponding Javadocs. The online Javadocs can be found here (bookmark it to your favorite browser).
Create a new Eclipse project, let's say under the name “XMPPProject”, and add the “smack.jar” and “smackx.jar” files into your classpath.
The main class that your client will use is XMPPConnection. As the name implies, it represents an XMPP connection to a remote server. Creating a connection and performing a login is as simple as the following code snippet:
// Create a connection to the igniterealtime.org XMPP server.
XMPPConnection connection = new XMPPConnection("myxmppserver.com");
// Connect to the server
connection.connect();
// Most servers require you to login before performing other tasks.
connection.login("myuser", "mypass");
Of course, a more sophisticated configuration of the connection attributes is possible and that can be achieved by using the ConnectionConfiguration class. There are multiple parameters that can be configured, with the most significant being the server host and port and various security (encryption) issues. You can also configure the internal Smack stack by using the SmackConfiguration class.
ConnectionConfiguration config =
new ConnectionConfiguration(serverHost, serverPort);
XMPPConnection connection = new XMPPConnection(config);
connection.connect();
connection.login("myuser", "mypass");
From a valid XMPPConnection, you can retrieve a ChatManager object. This one keeps track of references to all current chats and allows you to create Chat instances, i.e. series of messages sent between two users. Chat object are able of sending messages to other chat participants. You can use plain strings or construct XMPP message packets, represented by the Message class. Message class provides direct access to all of the message attributes, such as the message type. For processing incoming messages, you have to implement the MessageListener interface and attach it to a chat.
ChatManager chatManager = connection.getChatManager();
Chat chat = chatManager.createChat("mybuddy", new MyMessageListener());
chat.sendMessage(message);
You can also set your online status, also known as presence, by using the Presence class.
Presence presence = new Presence(Presence.Type.available);
presence.setStatus("What's up everyone?");
connection.sendPacket(presence);
A new buddy can be added to your list by using the Roster class. A user's roster is a collection of users a person receives presence updates for. A user can be associated with groups, but in the following example the new roster entry won't belong to a group.
Roster roster = connection.getRoster(); roster.createEntry(user, name, null);
The main class I created is named “XmppManager” and the source code is the following:
package com.javacodegeeks.xmpp;
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManager;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Presence.Type;
public class XmppManager {
private static final int packetReplyTimeout = 500; // millis
private String server;
private int port;
private ConnectionConfiguration config;
private XMPPConnection connection;
private ChatManager chatManager;
private MessageListener messageListener;
public XmppManager(String server, int port) {
this.server = server;
this.port = port;
}
public void init() throws XMPPException {
System.out.println(String.format("Initializing connection to server %1$s port %2$d", server, port));
SmackConfiguration.setPacketReplyTimeout(packetReplyTimeout);
config = new ConnectionConfiguration(server, port);
config.setSASLAuthenticationEnabled(false);
config.setSecurityMode(SecurityMode.disabled);
connection = new XMPPConnection(config);
connection.connect();
System.out.println("Connected: " + connection.isConnected());
chatManager = connection.getChatManager();
messageListener = new MyMessageListener();
}
public void performLogin(String username, String password) throws XMPPException {
if (connection!=null && connection.isConnected()) {
connection.login(username, password);
}
}
public void setStatus(boolean available, String status) {
Presence.Type type = available? Type.available: Type.unavailable;
Presence presence = new Presence(type);
presence.setStatus(status);
connection.sendPacket(presence);
}
public void destroy() {
if (connection!=null && connection.isConnected()) {
connection.disconnect();
}
}
public void sendMessage(String message, String buddyJID) throws XMPPException {
System.out.println(String.format("Sending mesage '%1$s' to user %2$s", message, buddyJID));
Chat chat = chatManager.createChat(buddyJID, messageListener);
chat.sendMessage(message);
}
public void createEntry(String user, String name) throws Exception {
System.out.println(String.format("Creating entry for buddy '%1$s' with name %2$s", user, name));
Roster roster = connection.getRoster();
roster.createEntry(user, name, null);
}
class MyMessageListener implements MessageListener {
@Override
public void processMessage(Chat chat, Message message) {
String from = message.getFrom();
String body = message.getBody();
System.out.println(String.format("Received message '%1$s' from %2$s", body, from));
}
}
}
Note that no security authentication is used and that the security mode is disabled. That means that there is no protection on the credentials exchange and that the messages are not secured. Smack API supports encryption, but I did not use it since I wanted to keep this tutorial simple.
Ok, let's see how all the above can be combined in order to create a simple IM application. I will be using the Openfire server installed in a previous tutorial, along with the two users that were created.
First, I use my Spark client to login as user “testuser2”. Nothing special here. Next, I am creating a test class that uses “XmppManager”, connects to my Openfire server, logins as user “testuser1”, sets the online status to “available”, adds the “testuser2” to the roster and finally sends a message. Note that I created an infinite loop so that I can send messages from the Spark client and see them to my console. I am using the “isRunning” boolean to continue the looping. In a real application, this would be set to false if the program was to exit. Make sure you set the thread to sleep for some time, else your machine's CPU will hit the roof. The test class is the following:
package com.javacodegeeks.xmpp;
public class XmppTest {
public static void main(String[] args) throws Exception {
String username = "testuser1";
String password = "testuser1pass";
XmppManager xmppManager = new XmppManager("myserver", 5222);
xmppManager.init();
xmppManager.performLogin(username, password);
xmppManager.setStatus(true, "Hello everyone");
String buddyJID = "testuser2";
String buddyName = "testuser2";
xmppManager.createEntry(buddyJID, buddyName);
xmppManager.sendMessage("Hello mate", "testuser2@myserver");
boolean isRunning = true;
while (isRunning) {
Thread.sleep(50);
}
xmppManager.destroy();
}
}
Make sure you select the appropriate username/password and server settings.
If you run the program, you should see the incoming message to the Spark client. Answer to that message and then check your Eclipse console to ensure that the message was printed.
Note that “testuser2” received the message even though he hadn't added “testuser1” as his buddy. This is the default behavior. Add the other user by hitting the “Add a Contact” button.

After the account is added, you will be able to see if the user is online and his current status as well.

Finally, you can retrieve the user's roster and check the status of the existing contacts.
public void printRoster() throws Exception {
Roster roster = connection.getRoster();
Collection<RosterEntry> entries = roster.getEntries();
for (RosterEntry entry : entries) {
System.out.println(String.format("Buddy:%1$s - Status:%2$s",
entry.getName(), entry.getStatus()));
}
}
That's all! As always, the Eclipse project created can be found here.
Happy coding!!!
Related Articles :
any chapter 4? :)
ReplyDeleteHi desgraci,
ReplyDeleteCurrently I don't have something pending for this series of tutorials. However, if you have any ideas on what else to include, I would be more than happy to hear them! Please let me know!
Regards,
Fabrizio
Great tutorial! Thank you of lot!!!
ReplyDeleteHi krasoffski,
ReplyDeleteThanks for your support!
Hi! Your tutorial helped me a lot. I am just wondering if you would know why I get this message: "couldn't setup local SOCKS5 proxy on port 7777: Address already in use: JVM_Bind" whenever I disconnect using the connection.disconnect()? Thanks!
ReplyDeleteHi Alvin,
ReplyDeleteI have no idea why this happens. I do not understand how a SOCKS proxy fits into the whole picture. Can you post the stack trace or the relevant error log?
Regards,
Fabrizio
Hi Fabrizio.
ReplyDeleteI can't get messages from the contacts unless I send a message to them. I wonder if it's because the Chat that was not created yet. For instance: if I login and send a message to contact abc@def.com, I'm able to get responses and keep chatting with abc@def.com. However if I don't send any message to abc@def.com, no matter how many messages abc@def.com send to me: I don't get anything, processMessage is never called.
Do you know why?
Hi Emerson,
ReplyDeleteYes, from a quick look, it appears that the cause of your problem is indeed the fact that the Chat class has not been created yet.
Try moving that initialization right after the "performLogin" execution, perhaps in a method of its own.
Let me know how this goes, in case I need to update the tutorial.
Regards,
Fabrizio
Hi again! I've already manage to fix that error by incorporating a try-catch block.
ReplyDeleteI would like to know how to get one's own nickname after logging in to his own account. I also would like to know how to add and reject users using the roster.setSubscriptionMode(Roster.SubscriptionMode.manual). How do you manage notification requests in smack. Once again, Thank you very much!
Hi Alvin,
ReplyDeleteI am not 100% sure, cause I haven't tried this myself before. As stated in the documentation:
http://www.igniterealtime.org/builds/smack/docs/latest/javadoc/org/jivesoftware/smack/Roster.SubscriptionMode.html#manual
you have to set the subscription mode to manual.
Then you have to implement a custom PacketListener
http://www.igniterealtime.org/builds/smack/docs/latest/javadoc/org/jivesoftware/smack/PacketListener.html
which will scan the incoming messages for packets of type Presence.Type.SUBSCRIBE. There you will provide your custom logic, i.e. accepting or rejecting the request.
The PacketListener is hooked up on a XMPPConnection with this method:
http://www.igniterealtime.org/builds/smack/docs/latest/javadoc/org/jivesoftware/smack/XMPPConnection.html#addPacketListener%28org.jivesoftware.smack.PacketListener,%20org.jivesoftware.smack.filter.PacketFilter%29
Hope that helps.
Cheers
Hello and thank you very much for this article!!
ReplyDeleteI'm a java beginner, so sorry for my question, and sorry for my english too (i'm from italy) :)
I noticed that, if i don't first call your sendMessage method, i can't receive messages from users, because i have to add a listener with it:
Chat chat = chatManager.createChat(buddyJID, messageListener);
Now my question: if i want to listen for messages from all contacts available in my roster, i need to createChat in loop for everyone? And the same when a new presence is added to the available list?
I did it, but i nee dto know if i'm going well :)
Thank you again.
Nicola
Hi Nicola,
ReplyDeleteIn general, you should not need to manually check about incoming messages or presence notifications.
Please have a look at the *Listener class from the Javadocs
http://www.igniterealtime.org/builds/smack/docs/latest/javadoc/index.html
You should use an event driven approach, i.e. let Smack notify you upon specific event.
For example, have a look at the PacketListener class:
http://www.igniterealtime.org/builds/smack/docs/latest/javadoc/org/jivesoftware/smack/PacketListener.html
Hope that helps!
Hi thank you very much, i'm trying it now :)
ReplyDeleteNicola
how to connect with sso? (single sign on) like the spark client?
ReplyDeleteHi,
ReplyDeleteconncection to the server is established but i am unable to login
Initializing connection to server localhost port 5222
Connected: true
Exception in thread "main" not-authorized(401)
at org.jivesoftware.smack.NonSASLAuthentication.authenticate(NonSASLAuthentication.java:110)
at org.jivesoftware.smack.XMPPConnection.login(XMPPConnection.java:404)
at org.jivesoftware.smack.XMPPConnection.login(XMPPConnection.java:349)
at com.javacodegeeks.xmpp.XmppManager.performLogin(XmppManager.java:59)
at com.javacodegeeks.xmpp.XmppTest.main(XmppTest.java:13)
Hey Sharique
ReplyDeleteDo you have any server running on your localhost at port 5222, if not you need one to do the authentication
Hi all,
ReplyDeleteCan somebody provide me some good tutorials, article, examples of using smack api with security authentication?
Hi All,
ReplyDeleteCan somebody guide me to add the roster entry using Roster.SubscriptionMode.manual.
how will develope somthing like the spark i.e the client...plz help..thanks n advance...
ReplyDeletecan any1 give me the code for registering a user to the server..thanks.
ReplyDeletedami hai dami
ReplyDeletethanks dude ... it really worked !!