This tutorial explains how to use the event admin service in OSGi applications and the IEventBroker in Eclipse RCP and plug-ins.
1. Communication within the Eclipse application
Parts of your application often need to update other components. Whenever possible, this communication should be decoupled, so that components are not tightly linked.
This can be achieved using a subscriber/publisher model, implemented as an event system. Software components can register for events, and other components can send events. The event system ensures that all registered components receive the events to which they subscribed.
1.1. The event bus in Eclipse and OSGi
The Eclipse platform and OSGi runtime provide a global, event-based communication system known as the event bus.
Any software component with access to the event system can send out arbitrary events, as shown in the following graphic.

The Eclipse platform ensures that registered components receive the messages. It uses this event system for internal communication.
1.2. Selecting the object type to send
The event system allows for sending and receiving objects of any type. Often, it is enough to send the desired object through an event. However, it is good practice to send a map-like structure, which gives all events a common format.
2. Event service in Eclipse
The Eclipse framework provides and uses the IEventBroker event service, which allows the components of your application to communicate with each other.
The Eclipse event service is based on the OSGi EventAdmin service.
2.1. Required plug-ins and packages for using the event service
The IEventBroker service is provided by the org.eclipse.e4.core.services plug-in.
To use OSGi types such as EventHandler directly, your plug-in also needs to import the org.osgi.service.event package.
2.2. Sending events in Eclipse
The event service can be injected using dependency injection.
@Inject
private IEventBroker eventBroker;
The event service sends notifications to registered components, either asynchronously or synchronously.
The following examples assume that a TaskEventConstants interface defines the topics as string constants.
// asynchronously, the calling code is not blocked until the event has been delivered
eventBroker.post(TaskEventConstants.TOPIC_TASKS_NEW, task);
// synchronously, the calling code is blocked until delivery
eventBroker.send(TaskEventConstants.TOPIC_TASKS_NEW, task);
You can send arbitrary Java objects or primitives through the event bus.
2.3. Registering to receive events
You can use dependency injection to register and respond to events. In this setup, the Eclipse framework automatically removes all event subscriptions when the object is disposed.
The @EventTopic and @UIEventTopic annotations tag methods and fields that should be notified of events.
The @UIEventTopic annotation ensures that event notifications occur in the main thread.
@Inject
@Optional
private void subscribeTopicTaskUpdated(@UIEventTopic(TaskEventConstants.TOPIC_TASKS_UPDATE) Task task) {
if (viewer != null) {
// reload the input, as this example does not use data binding for the viewer
taskService.consume(viewer::setInput);
}
}
You can register an instance of org.osgi.service.event.EventHandler directly using the IEventBroker.subscribe() method.
To unsubscribe, use the unsubscribe() method.
|
Using dependency injection for subscribing is preferred over direct subscription. This way, the framework manages listener registration and deregistration automatically. However, sometimes it is useful to control when to listen to events, for example, when an event should only be received once. |
import jakarta.annotation.PostConstruct;
import jakarta.inject.Inject;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.e4.ui.model.application.ui.advanced.MPerspective;
import org.eclipse.e4.ui.workbench.UIEvents;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
public class LazyLoadingAddon implements EventHandler {
@Inject
private IEventBroker broker;
@PostConstruct
public void lazyLoadInContributorPerspective() {
broker.subscribe(UIEvents.ElementContainer.TOPIC_SELECTEDELEMENT, this);
}
@Override
public void handleEvent(Event event) {
// only load data when this particular perspective is selected
if (event.getProperty(UIEvents.EventTags.NEW_VALUE) instanceof MPerspective perspective
&& "com.example.speciallazyloaded.perspective".equals(perspective.getElementId())) {
// loading data...
// unsubscribe afterwards, so that loading is only done once
broker.unsubscribe(this);
}
}
}
2.4. Subscribing to sub-topics
You can subscribe to specific topics or use wildcards to subscribe to all sub-events.
Sub-events are separated by /.
The following example defines string constants, including the TOPIC_TASKS_ALLTOPICS constant, which can be used to register for all sub-events.
package com.vogella.tasks.events;
/**
* Only used for constant definition.
*
* @noimplement This interface is not intended to be implemented by clients.
*/
public interface TaskEventConstants {
// topic identifier for all topics
String TOPIC_TASKS = "TOPIC_TASKS";
// this key can only be used for event registration, you cannot
// send out generic events
String TOPIC_TASKS_ALLTOPICS = "TOPIC_TASKS/*";
String TOPIC_TASKS_CHANGED = "TOPIC_TASKS/CHANGED";
String TOPIC_TASKS_NEW = "TOPIC_TASKS/TASKS/NEW";
String TOPIC_TASKS_DELETE = "TOPIC_TASKS/TASKS/DELETED";
String TOPIC_TASKS_UPDATE = "TOPIC_TASKS/TASKS/UPDATED";
}
2.5. Event system compared to Eclipse context
The IEventBroker is a global event bus and is independent of the IEclipseContext hierarchy.
The IEventBroker service supports sending event information without requiring knowledge of the recipient.
Interested classes can register for events without knowing which classes provide them.
This promotes loosely coupled application components.
The disadvantage is that it is a global bus with no scoping of events. Publishers must ensure they provide enough information in the topic and sent objects to enable subscribers to determine the relevance of each event.
2.6. Asynchronous processing and the event bus
Background threads can use the IEventBroker to send event data.
A method annotated with @UIEventTopic receives it in the main thread, so it can update the user interface directly.
private static final String UPDATE = "update";
@Inject
IEventBroker broker;
// somewhere in your code you start a long-running task
button.addSelectionListener(SelectionListener.widgetSelectedAdapter(e ->
Thread.ofVirtual().start(() -> {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
return;
}
// send out an event to update the UI
broker.send(UPDATE, i);
}
})));
// more code
// ....
// called in the UI thread
@Inject
@Optional
public void getEvent(@UIEventTopic(UPDATE) int i) {
// text1 is an SWT Text field
text1.setText(String.valueOf(i));
}
2.7. Evaluation of the can execute method for handlers
The framework calls a method annotated with @CanExecute if a change occurs in the Eclipse context, for example, when the user selects a new part.
If the method returns false, the framework disables any menu and tool items associated with that command.
You can request a re-evaluation of the @CanExecute methods by sending an event via the event broker.
// evaluate all @CanExecute methods
eventBroker.post(UIEvents.REQUEST_ENABLEMENT_UPDATE_TOPIC, UIEvents.ALL_ELEMENT_ID);
// evaluate only the element with this element ID
// (alternatively, pass an org.eclipse.e4.ui.workbench.Selector)
eventBroker.post(UIEvents.REQUEST_ENABLEMENT_UPDATE_TOPIC, "com.example.toolitem.id");
2.8. The event bus in OSGi
OSGi provides the EventAdmin for handling event-based communication.
2.9. Required packages for using the event admin
Your plug-in needs to import the org.osgi.service.event package to use the OSGi event service directly.
2.10. Sending events to the IEventBroker via an OSGi service
The IEventBroker used in Eclipse applications is based on the OSGi Event Admin service.
Thus, you can use the OSGi Event Admin to send events to your Eclipse application, for example, from another OSGi service.
Put the payload under the org.eclipse.e4.data key (the IEventBroker.DATA constant) into the event properties, so that it is injected into @EventTopic and @UIEventTopic methods.
The following OSGi service uses the OSGi Event Admin to send out an event after 5 seconds.
package com.vogella.osgi.eventadminuser;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
@Component
public class EventAdminUser {
@Reference
EventAdmin admin;
@Activate
public void activate() {
String payload = "Hello from OSGi";
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.schedule(() -> admin.sendEvent(new Event("YOURKEY", Map.of("org.eclipse.e4.data", payload))), 5,
TimeUnit.SECONDS);
// already scheduled tasks still run after shutdown
executor.shutdown();
}
}
If a part containing the following code is open, it receives this event 5 seconds after the service has been activated.
@Inject
@Optional
public void getFromOSGi(@EventTopic("YOURKEY") String value) {
System.out.println(value);
}
2.11. Wrapper for event processing
The OSGi Event Admin API is verbose, so you might wrap it for easier access.
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import java.util.HashMap;
import java.util.Map;
public class EventPublisher {
private final EventAdmin eventAdmin;
private final Map<String, Object> properties = new HashMap<>();
private String topic;
public EventPublisher(EventAdmin eventAdmin) {
this.eventAdmin = eventAdmin;
}
public static EventPublisher with(EventAdmin eventAdmin) {
return new EventPublisher(eventAdmin);
}
public EventPublisher topic(String topic) {
this.topic = topic;
return this;
}
public EventPublisher property(String key, Object value) {
properties.put(key, value);
return this;
}
// asynchronous delivery
public void send() {
if (topic == null || topic.isEmpty()) {
throw new IllegalStateException("Event topic cannot be null or empty");
}
Event event = new Event(topic, properties);
eventAdmin.postEvent(event);
}
}
This simplifies sending events.
import org.eclipse.e4.core.services.events.IEventBroker;
import org.osgi.service.event.EventAdmin;
import com.vogella.tasks.events.TaskEventConstants;
import com.vogella.tasks.model.Task;
public class EventSenderExample {
private final EventAdmin eventAdmin;
public EventSenderExample(EventAdmin eventAdmin) {
this.eventAdmin = eventAdmin;
}
public void sendUpdateEvent(Task task) {
EventPublisher.with(eventAdmin)
.topic(TaskEventConstants.TOPIC_TASKS_UPDATE)
.property(IEventBroker.DATA, task)
.send();
}
}
You could also add a static lookup() method to the EventPublisher for retrieving the Event Admin service.
public static EventAdmin lookup() {
BundleContext context = FrameworkUtil.getBundle(EventPublisher.class).getBundleContext();
ServiceReference<EventAdmin> ref = context.getServiceReference(EventAdmin.class);
EventAdmin eventAdmin = ref == null ? null : context.getService(ref);
if (eventAdmin == null) {
throw new IllegalStateException("EventAdmin service not available");
}
return eventAdmin;
}
3. Exercise: Use the event admin service in OSGi
In OSGi, you can easily consume and send out events. Event registration is done by registering services, while the event admin can be used to send out events to them.
Assume you have the following constants.
public interface VogellaEventConstants {
public static final String TOPIC_BASE = "com/vogella/events/";
public static final String TOPIC_ALL = TOPIC_BASE + "*";
public static final String TOPIC_HELP = TOPIC_BASE + "HELP";
public static final String TOPIC_UPDATE = TOPIC_BASE + "UPDATE";
public static final String PROPERTY_KEY_TARGET = "target";
}
This OSGi service will produce an event.
package app.services;
import java.util.HashMap;
import java.util.Map;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
@Component
public class EventProducer {
public static final String TOPIC_BASE = "com/vogella/events/";
public static final String TOPIC_HELP = TOPIC_BASE + "HELP";
public static final String TOPIC_UPDATE = TOPIC_BASE + "UPDATE";
@Activate
public EventProducer(@Reference EventAdmin admin) {
System.out.println(admin);
Map<String, Object> properties = new HashMap<>();
properties.put(VogellaEventConstants.PROPERTY_KEY_TARGET, "Hello World");
Event event = new Event(VogellaEventConstants.TOPIC_UPDATE, properties);
admin.postEvent(event);
}
}
The following OSGi service registers for this event and receives it.
package app.services;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
@Component(property = EventConstants.EVENT_TOPIC + "=" + VogellaEventConstants.TOPIC_UPDATE)
public class EventConsumer implements EventHandler {
@Override
public void handleEvent(Event event) {
System.out.println("I will now update: "
+ event.getProperty(VogellaEventConstants.PROPERTY_KEY_TARGET));
}
}
It is possible to register for all events.
package app.services;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
@Component(property = EventConstants.EVENT_TOPIC + "=" + VogellaEventConstants.TOPIC_ALL)
public class EventConsumerSubtopics implements EventHandler {
@Override
public void handleEvent(Event event) {
String[] propertyNames = event.getPropertyNames();
for (String name : propertyNames) {
System.out.println("Property name: " + name+ " with value: " + event.getProperty(name));
}
}
}
And you can register event handlers dynamically as a new service.
package app.services;
import java.util.Dictionary;
import java.util.Hashtable;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
@Component
public class DynamicEventSubscriber {
@Reference
private EventAdmin eventAdmin;
private ServiceRegistration<EventHandler> registration;
private BundleContext bundleContext;
@Activate
public void startListening(@Reference BundleContext bundleContext) {
this.bundleContext = bundleContext;
startListening(VogellaEventConstants.TOPIC_UPDATE);
}
public void startListening(String topic) {
Dictionary<String, Object> properties = new Hashtable<>();
properties.put(EventConstants.EVENT_TOPIC, topic);
// Register the EventHandler dynamically
registration = bundleContext.registerService(EventHandler.class, new EventHandler() {
@Override
public void handleEvent(Event event) {
System.out.println("DynamicEventHandler: Received event: " + event.getTopic() + ", target: "
+ event.getProperty("target"));
}
}, properties);
System.out.println("Listening to topic: " + topic);
}
@Deactivate
public void stopListening() {
if (registration != null) {
registration.unregister();
registration = null;
System.out.println("Stopped listening to events.");
}
}
}
4. Exercise: Use the event admin service in OSGi
4.1. Create plug-in for events
Create a new plug-in named com.vogella.events.
Create a new class VogellaEventConstants.
package com.vogella.events;
public final class VogellaEventConstants {
private VogellaEventConstants() {
// private default constructor for constants class
// to avoid someone extends the class
}
public static final String TOPIC_BASE = "com/vogella/events/";
public static final String TOPIC_HELP = TOPIC_BASE + "HELP";
public static final String TOPIC_UPDATE= TOPIC_BASE + "UPDATE";
public static final String TOPIC_SWITCH= TOPIC_BASE + "SWITCH";
public static final String TOPIC_ALL = TOPIC_BASE + "*";
public static final String PROPERTY_KEY_TARGET = "target";
}
Export the package of this class as an API via the Runtime tab of the manifest editor.
4.2. Create plug-in for consumer
Add org.osgi.service.event as a package dependency to the manifest of com.vogella.osgi.console.
Add a package dependency to com.vogella.events in com.vogella.osgi.console via the Dependency tab of its manifest editor.
Create a new class named EventCommandSender in your com.vogella.osgi.console plug-in.
package com.vogella.osgi.console;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.osgi.framework.console.CommandInterpreter;
import org.eclipse.osgi.framework.console.CommandProvider;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import com.vogella.events.VogellaEventConstants;
@Component
public class EventCommandSender implements CommandProvider {
@Reference
EventAdmin eventAdmin;
public void _sendEvent(CommandInterpreter ci) {
String command = ci.nextArgument();
if (command == null)
{
command = "";
}
// create the event properties object
Map<String, Object> properties = new HashMap<>();
properties.put(VogellaEventConstants.PROPERTY_KEY_TARGET, command);
Event event = null;
switch (command) {
case "help":
event = new Event(VogellaEventConstants.TOPIC_HELP, properties);
break;
case "update":
event = new Event(VogellaEventConstants.TOPIC_UPDATE, properties);
break;
case "switch":
event = new Event(VogellaEventConstants.TOPIC_SWITCH, properties);
break;
default:
System.out.println(command + " is not known as event! See help sendEvent");
}
if (event != null) {
eventAdmin.postEvent(event);
}
}
@Override
public String getHelp() {
return "Allows to trigger events via the command line, options: help, update, switch";
}
}
4.3. Create plug-in for receiver
Create a new plug-in named com.vogella.osgi.eventreceiver.
Add the following dependencies on the Imported Packages side:
-
com.vogella.events
-
org.osgi.service.event
Implement the following class, which reacts to the events.
package com.vogella.osgi.eventreceiver;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
import com.vogella.events.VogellaEventConstants;
@Component(
property = EventConstants.EVENT_TOPIC
+ "=" + VogellaEventConstants.TOPIC_UPDATE)
public class EventReaction implements EventHandler {
@Override
public void handleEvent(Event event) {
System.out.println("I will now update: "
+ event.getProperty(VogellaEventConstants.PROPERTY_KEY_TARGET));
}
}
4.4. Add required plug-ins to your runtime
Add the following plug-ins to your product (via the feature).
-
org.eclipse.equinox.event -
com.vogella.osgi.eventreceiver
4.5. Handle multiple event topics
Your event receiver can handle multiple events. Change your event handler to receive multiple events.
package com.vogella.osgi.eventreceiver;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
import com.vogella.events.VogellaEventConstants;
@Component(
(1)
property = {
EventConstants.EVENT_TOPIC + "=" + VogellaEventConstants.TOPIC_UPDATE,
EventConstants.EVENT_TOPIC + "=" + VogellaEventConstants.TOPIC_SWITCH
})
public class EventReaction implements EventHandler {
@Override
public void handleEvent(Event event) {
System.out.println("I will now update: "
+ event.getProperty(VogellaEventConstants.PROPERTY_KEY_TARGET));
}
}
| 1 | The service property event.topics is declared multiple times via the @Component annotation type element property. This way, an array of Strings is configured for the service property, so the handler reacts on both topics. |
4.6. Register for wildcard events
It is possible to register for all events of a certain subtype.
Create a new event handler and register it for the VogellaEventConstants.TOPIC_ALL event.
Ensure that it is called if you send any event of your known event types.
4.7. Filter
You can use an LDAP-type filter to filter on event properties. Change your above implementation to be triggered for all events, except for the "update" event.
@Component(
property = {
EventConstants.EVENT_TOPIC + "=" + VogellaEventConstants.TOPIC_ALL,
EventConstants.EVENT_FILTER + "=" + "=" + "(!(target=update))"})
Validate that your WildcardEventReceiver is not triggered if the update event is sent.
5. Learn more and get support
This tutorial continues on Eclipse RCP online training or Eclipse IDE extensions with lots of video material, additional exercises and much more content.
6. Links and literature
Wiki for the Event Model of Eclipse 4 Wiki for the event processing https://bugs.eclipse.org/bugs/show_bug.cgi?id=427465 Bug and implementation report about enabling handlers via events
6.1. vogella Java example code
If you need more assistance we offer Online Training and Onsite training as well as consulting