Developing Plug-in Event Handlers
๐ฏ Goalโ
This tutorial shows you how to develop event handlers in your own plug-in, enabling you to intercept and respond to data model events throughout the iDempiere system.
What is an Event Handler?โ
An event handler is a component that listens to and responds to events triggered by changes to data models (tables) in iDempiere. Event handlers are executed whenever records are created, updated, or deleted, regardless of how the change was initiated.
Event handlers are commonly used to:
- Validate data across all entry points (UI, REST API, processes, imports)
- Enforce business rules that must apply system-wide
- Trigger automated actions when data changes (e.g., send notifications, update related records)
- Maintain data integrity through cross-table validations
- Audit and log specific operations
- Integrate with external systems when data changes
For example, when a Sales Order is completed, an event handler can automatically create a shipment, send a notification email, or update inventory levels.
Event Handlers vs Calloutsโ
| Feature | Event Handler | Callout |
|---|---|---|
| Scope | All operations (UI, API, processes, imports) | UI and CSV Importer only |
| Execution Point | Before/after database operations | During field value changes in UI |
| Use Case | System-wide business rules and validations | UI-specific field interactions |
Use event handlers when you need logic that applies regardless of how data is entered or modified. Use callouts for UI-specific interactions like auto-filling fields or showing immediate feedback to users.
โ Prerequisitesโ
Before starting, review:
You should already know how to create a plug-in, as this guide will not cover that in detail.
๐ Overviewโ
iDempiere's event handling system is built on top of the OSGi Event Admin Framework. The IEventManager service, provided by the org.adempiere.base bundle, enables you to register event handlers that replace the legacy AD_ModelValidator table and ModelValidator interface approach.
๐ Implementation Methodsโ
Modern Approach (iDempiere 9+): Event Annotationโ
Starting with iDempiere 9, you can use annotations to register event handlers. This is the simplest and recommended approach.
For details, see the section on Event Annotations below.
Creating an Event Handler Classโ
Step 1: Extend AbstractEventHandlerโ
Create a class that extends AbstractEventHandler. This base class simplifies event handler implementation and allows you to stop events by throwing a RuntimeException.
Example: MyEventHandler.java
package com.example.eventhandler;
import org.adempiere.base.event.AbstractEventHandler;
import org.adempiere.base.event.IEventTopics;
import org.compiere.model.PO;
import org.compiere.model.MOrder;
import org.osgi.service.event.Event;
public class MyEventHandler extends AbstractEventHandler {
@Override
protected void initialize() {
// Register for events on the C_Order table
registerTableEvent(IEventTopics.PO_BEFORE_NEW, MOrder.Table_Name);
registerTableEvent(IEventTopics.PO_BEFORE_CHANGE, MOrder.Table_Name);
registerTableEvent(IEventTopics.PO_AFTER_NEW, MOrder.Table_Name);
}
@Override
protected void doHandleEvent(Event event) {
String type = event.getTopic();
PO po = getPO(event);
if (po instanceof MOrder) {
MOrder order = (MOrder) po;
if (type.equals(IEventTopics.PO_BEFORE_NEW)) {
// Validation logic before creating a new order
if (order.getC_BPartner_ID() <= 0) {
throw new RuntimeException("Business Partner is required");
}
} else if (type.equals(IEventTopics.PO_BEFORE_CHANGE)) {
// Validation logic before updating an order
validateOrderTotal(order);
} else if (type.equals(IEventTopics.PO_AFTER_NEW)) {
// Post-creation actions
System.out.println("New order created: " + order.getDocumentNo());
}
}
}
private void validateOrderTotal(MOrder order) {
// Your validation logic
if (order.getGrandTotal().signum() < 0) {
throw new RuntimeException("Order total cannot be negative");
}
}
}
Example Project:
For a complete working example of event handlers, see:
idempiere-examples: Event Handler Example
Step 2: Override Required Methodsโ
Your event handler class must override two methods:
initialize()- Register the table events you're interested indoHandleEvent(Event event)- Implement your business logic
Available Event Topics (from IEventTopics interface):
Model (PO) Eventsโ
These events are triggered during record operations:
PO_BEFORE_NEW- Before a new record is saved to the databasePO_AFTER_NEW- After a new record is saved to the databasePO_AFTER_NEW_REPLICATION- After new record replicationPO_BEFORE_CHANGE- Before an existing record is updatedPO_AFTER_CHANGE- After an existing record is updatedPO_AFTER_CHANGE_REPLICATION- After record change replicationPO_BEFORE_DELETE- Before a record is deletedPO_AFTER_DELETE- After a record is deletedPO_BEFORE_DELETE_REPLICATION- Before record deletion replicationPO_POST_CREATE- Asynchronous event after record creationPO_POST_UPDATE- Asynchronous event after record updatePO_POST_DELETE- Asynchronous event after record deletionPO_ALL- Wildcard to match all PO events
Document Eventsโ
These events are triggered during document workflow actions:
Before Events:
DOC_BEFORE_PREPARE- Before document is preparedDOC_BEFORE_COMPLETE- Before document is completedDOC_BEFORE_VOID- Before document is voidedDOC_BEFORE_CLOSE- Before document is closedDOC_BEFORE_REACTIVATE- Before document is reactivatedDOC_BEFORE_REVERSECORRECT- Before document reverse correctionDOC_BEFORE_REVERSEACCRUAL- Before document reverse accrualDOC_BEFORE_POST- Before document is posted to accounting
After Events:
DOC_AFTER_PREPARE- After document is preparedDOC_AFTER_COMPLETE- After document is completedDOC_AFTER_VOID- After document is voidedDOC_AFTER_CLOSE- After document is closedDOC_AFTER_REACTIVATE- After document is reactivatedDOC_AFTER_REVERSECORRECT- After document reverse correctionDOC_AFTER_REVERSEACCRUAL- After document reverse accrualDOC_AFTER_POST- After document is posted to accountingDOC_ALL- Wildcard to match all document events
Import Eventsโ
Events triggered during data import processes:
IMPORT_BEFORE_VALIDATE- Before all import records are validatedIMPORT_AFTER_VALIDATE- After all import records are validatedIMPORT_BEFORE_IMPORT- Before an import record is processedIMPORT_AFTER_IMPORT- After an import record is processed
Process Eventsโ
Events triggered during process execution:
BEFORE_PROCESS- Before starting a process, after it's preparedAFTER_PROCESS- After a process finishes, before commitPOST_PROCESS- After a process is committed (asynchronous)
System Eventsโ
Other system-level events:
AFTER_LOGIN- After user loginACCT_FACTS_VALIDATE- During accounting facts validationPREF_AFTER_LOAD- After preferences are loadedDOCACTION- During discovery of available document actionsBROADCAST_MESSAGE- For broadcast messagingREQUEST_SEND_EMAIL- Before sending email for requestsREPORT_SEND_EMAIL- Before sending email for reports (to prefill dialog variables)
- Use BEFORE events for validation and preventing operations
- Use AFTER events for post-processing and triggering subsequent actions
- Use POST events for asynchronous operations that shouldn't block the main transaction
- Use DOC events for document-specific workflow logic
- Use PO events for general record-level logic
Instead of throwing an exception, you can call addErrorMessage(String message) to display a user-friendly error message without a stack trace.
๐ง Registering as an OSGi Componentโ
Option A: Using Annotations (Recommended)โ
Add the @Component annotation to your event handler class:
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@Component(
service = org.osgi.service.event.EventHandler.class,
immediate = true
)
public class MyEventHandler extends AbstractEventHandler {
@Reference
public void bindEventManager(IEventManager eventManager) {
super.setEventManager(eventManager);
}
public void unbindEventManager(IEventManager eventManager) {
super.setEventManager(null);
}
// ... rest of implementation
}
See Event Handling with Annotations for more details.
Option B: Manual Component Definitionโ
If not using annotations, create a component definition XML file manually.
Step 1: Create OSGI-INF Directoryโ
Create a directory named OSGI-INF in your project root (this is the OSGi convention for component definitions).
Step 2: Create Component Definition XMLโ
Create a file OSGI-INF/eventhandler.xml:
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
name="com.example.eventhandler.MyEventHandler">
<implementation class="com.example.eventhandler.MyEventHandler"/>
<reference
bind="bindEventManager"
unbind="unbindEventManager"
cardinality="1..1"
interface="org.adempiere.base.event.IEventManager"
name="IEventManager"
policy="static"/>
</scr:component>
The bindEventManager and unbindEventManager methods are inherited from the AbstractEventHandler class.
Step 3: Update MANIFEST.MFโ
Edit your META-INF/MANIFEST.MF file to include the component definition:
Service-Component: OSGI-INF/eventhandler.xml
If you have multiple component definitions, you can use:
Service-Component: OSGI-INF/*.xml
Step 4: Enable Plugin Activationโ
In your MANIFEST.MF, ensure the plugin activation is enabled:
Bundle-ActivationPolicy: lazy
Also ensure in your plugin configuration that "Activate this plugin when one of its classes is loaded" is enabled.
Step 5: Update build.propertiesโ
Ensure build.properties includes the OSGI-INF directory:
bin.includes = META-INF/,\
.,\
OSGI-INF/
๐งช Testing Your Event Handlerโ
- Deploy your plug-in to iDempiere
- Start the iDempiere server
- Perform an action that triggers your event (e.g., create or update a Sales Order)
- Verify that your event handler logic executes
- Check logs for any output or errors
๐ฆ Exporting Your Plug-inโ
Before exporting your plug-in as a JAR:
- Open
build.properties - Verify that
OSGI-INF/is listed in Binary Build - Ensure your component definition XML files are included
- Test the exported JAR in a clean iDempiere instance
๐ฅ Video Tutorialโ
Watch this video walkthrough:
https://www.youtube.com/watch?v=zc_Ye8WZ-jc
Additional tutorial:
https://www.youtube.com/watch?v=lVPPnyiBvV8