Event Manager Interface
TheIEventManager interface defines the contract for event managers:
public interface IEventManager {
void register(Object listener);
void unregister(Object listener);
void handle(GenericEvent event);
List<Object> getRegisteredListeners();
}
Built-in Event Managers
JDA provides two default implementations:InterfacedEventManager
The default event manager that uses theEventListener interface:
import net.dv8tion.jda.api.hooks.EventListener;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
public class MyListener extends ListenerAdapter {
@Override
public void onMessageReceived(MessageReceivedEvent event) {
System.out.println("Message: " + event.getMessage().getContentRaw());
}
}
AnnotatedEventManager
Uses the@SubscribeEvent annotation to mark event handler methods:
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.hooks.AnnotatedEventManager;
import net.dv8tion.jda.api.hooks.SubscribeEvent;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
public class AnnotatedListener {
@SubscribeEvent
public void onMessage(MessageReceivedEvent event) {
System.out.println("Message: " + event.getMessage().getContentRaw());
}
}
// Usage
JDABuilder.createDefault(token)
.setEventManager(new AnnotatedEventManager())
.addEventListeners(new AnnotatedListener())
.build();
Creating a Custom Event Manager
Priority-Based Event Manager
Implement an event manager that handles events based on listener priority:import net.dv8tion.jda.api.hooks.IEventManager;
import net.dv8tion.jda.api.events.GenericEvent;
import java.lang.annotation.*;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
public class PriorityEventManager implements IEventManager {
private final Map<Class<?>, List<EventHandler>> handlers = new ConcurrentHashMap<>();
private final List<Object> listeners = new CopyOnWriteArrayList<>();
@Override
public void register(Object listener) {
listeners.add(listener);
for (Method method : listener.getClass().getMethods()) {
if (method.isAnnotationPresent(EventHandler.class)) {
Class<?>[] params = method.getParameterTypes();
if (params.length == 1 && GenericEvent.class.isAssignableFrom(params[0])) {
Class<?> eventType = params[0];
int priority = method.getAnnotation(EventHandler.class).priority();
handlers.computeIfAbsent(eventType, k -> new ArrayList<>())
.add(new HandlerMethod(listener, method, priority));
// Sort by priority (higher = earlier execution)
handlers.get(eventType).sort(
Comparator.comparingInt(h -> -h.priority)
);
}
}
}
}
@Override
public void unregister(Object listener) {
listeners.remove(listener);
handlers.values().forEach(list ->
list.removeIf(h -> h.listener == listener)
);
}
@Override
public void handle(GenericEvent event) {
Class<?> eventClass = event.getClass();
// Check all handler lists for matching event types
for (Map.Entry<Class<?>, List<HandlerMethod>> entry : handlers.entrySet()) {
if (entry.getKey().isAssignableFrom(eventClass)) {
for (HandlerMethod handler : entry.getValue()) {
try {
handler.method.invoke(handler.listener, event);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
@Override
public List<Object> getRegisteredListeners() {
return new ArrayList<>(listeners);
}
// Inner classes
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface EventHandler {
int priority() default 0;
}
private static class HandlerMethod {
final Object listener;
final Method method;
final int priority;
HandlerMethod(Object listener, Method method, int priority) {
this.listener = listener;
this.method = method;
this.priority = priority;
method.setAccessible(true);
}
}
}
Usage Example
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
public class PriorityListenerExample {
public static class HighPriorityListener {
@PriorityEventManager.EventHandler(priority = 100)
public void onMessage(MessageReceivedEvent event) {
System.out.println("High priority: " + event.getMessage());
}
}
public static class LowPriorityListener {
@PriorityEventManager.EventHandler(priority = 1)
public void onMessage(MessageReceivedEvent event) {
System.out.println("Low priority: " + event.getMessage());
}
}
public void build(String token) {
JDABuilder.createDefault(token)
.setEventManager(new PriorityEventManager())
.addEventListeners(
new HighPriorityListener(),
new LowPriorityListener()
)
.build();
}
}
Async Event Manager
Handle events asynchronously to avoid blocking:import net.dv8tion.jda.api.hooks.IEventManager;
import net.dv8tion.jda.api.hooks.EventListener;
import net.dv8tion.jda.api.events.GenericEvent;
import java.util.*;
import java.util.concurrent.*;
public class AsyncEventManager implements IEventManager {
private final List<EventListener> listeners = new CopyOnWriteArrayList<>();
private final ExecutorService executor;
public AsyncEventManager() {
this(Executors.newCachedThreadPool());
}
public AsyncEventManager(ExecutorService executor) {
this.executor = executor;
}
@Override
public void register(Object listener) {
if (listener instanceof EventListener) {
listeners.add((EventListener) listener);
} else {
throw new IllegalArgumentException(
"Listener must implement EventListener"
);
}
}
@Override
public void unregister(Object listener) {
listeners.remove(listener);
}
@Override
public void handle(GenericEvent event) {
for (EventListener listener : listeners) {
executor.submit(() -> {
try {
listener.onEvent(event);
} catch (Throwable t) {
System.err.println("Error handling event: " + t.getMessage());
t.printStackTrace();
}
});
}
}
@Override
public List<Object> getRegisteredListeners() {
return new ArrayList<>(listeners);
}
public void shutdown() {
executor.shutdown();
}
}
Filtered Event Manager
Filter events before dispatching them:import net.dv8tion.jda.api.hooks.IEventManager;
import net.dv8tion.jda.api.hooks.InterfacedEventManager;
import net.dv8tion.jda.api.events.GenericEvent;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import java.util.function.Predicate;
public class FilteredEventManager implements IEventManager {
private final IEventManager delegate = new InterfacedEventManager();
private final Predicate<GenericEvent> filter;
public FilteredEventManager(Predicate<GenericEvent> filter) {
this.filter = filter;
}
@Override
public void register(Object listener) {
delegate.register(listener);
}
@Override
public void unregister(Object listener) {
delegate.unregister(listener);
}
@Override
public void handle(GenericEvent event) {
if (filter.test(event)) {
delegate.handle(event);
}
}
@Override
public List<Object> getRegisteredListeners() {
return delegate.getRegisteredListeners();
}
}
// Usage: Only handle messages from non-bots
FilteredEventManager manager = new FilteredEventManager(event -> {
if (event instanceof MessageReceivedEvent) {
MessageReceivedEvent msgEvent = (MessageReceivedEvent) event;
return !msgEvent.getAuthor().isBot();
}
return true;
});
Event Metrics Manager
Track event handling metrics:import net.dv8tion.jda.api.hooks.IEventManager;
import net.dv8tion.jda.api.hooks.InterfacedEventManager;
import net.dv8tion.jda.api.events.GenericEvent;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
public class MetricsEventManager implements IEventManager {
private final IEventManager delegate = new InterfacedEventManager();
private final Map<Class<?>, AtomicLong> eventCounts = new ConcurrentHashMap<>();
private final Map<Class<?>, AtomicLong> totalTime = new ConcurrentHashMap<>();
@Override
public void register(Object listener) {
delegate.register(listener);
}
@Override
public void unregister(Object listener) {
delegate.unregister(listener);
}
@Override
public void handle(GenericEvent event) {
Class<?> eventClass = event.getClass();
long startTime = System.nanoTime();
try {
delegate.handle(event);
} finally {
long duration = System.nanoTime() - startTime;
eventCounts.computeIfAbsent(eventClass, k -> new AtomicLong())
.incrementAndGet();
totalTime.computeIfAbsent(eventClass, k -> new AtomicLong())
.addAndGet(duration);
}
}
@Override
public List<Object> getRegisteredListeners() {
return delegate.getRegisteredListeners();
}
public Map<String, EventMetrics> getMetrics() {
Map<String, EventMetrics> metrics = new HashMap<>();
for (Map.Entry<Class<?>, AtomicLong> entry : eventCounts.entrySet()) {
Class<?> eventClass = entry.getKey();
long count = entry.getValue().get();
long time = totalTime.get(eventClass).get();
metrics.put(
eventClass.getSimpleName(),
new EventMetrics(count, time, time / count)
);
}
return metrics;
}
public static class EventMetrics {
public final long count;
public final long totalNanos;
public final long avgNanos;
public EventMetrics(long count, long totalNanos, long avgNanos) {
this.count = count;
this.totalNanos = totalNanos;
this.avgNanos = avgNanos;
}
}
}
When implementing a custom event manager, ensure it’s thread-safe. JDA may call the
handle() method from multiple threads simultaneously.Configuring Event Managers
Set your custom event manager during JDA initialization:import net.dv8tion.jda.api.JDABuilder;
public class CustomManagerSetup {
public void build(String token) {
// Priority-based manager
JDABuilder.createDefault(token)
.setEventManager(new PriorityEventManager())
.build();
// Async manager
JDABuilder.createDefault(token)
.setEventManager(new AsyncEventManager())
.build();
// Metrics manager
MetricsEventManager metricsManager = new MetricsEventManager();
JDABuilder.createDefault(token)
.setEventManager(metricsManager)
.build();
// Later, retrieve metrics
Map<String, MetricsEventManager.EventMetrics> metrics =
metricsManager.getMetrics();
}
}
Best Practices
- Thread safety: Ensure your event manager is thread-safe, especially when managing listener lists
- Error handling: Always catch and handle exceptions in event handlers to prevent crashes
- Performance: Avoid heavy processing in the
handle()method; delegate to thread pools if needed - Documentation: Clearly document your event manager’s behavior and requirements
- Testing: Thoroughly test your event manager with various event types and listener configurations
- Delegation: Consider delegating to built-in managers when possible to leverage tested implementations
Common Use Cases
Event Debouncing
// Prevent rapid-fire event handling
public class DebouncedEventManager implements IEventManager {
private final Map<Class<?>, Long> lastEventTime = new ConcurrentHashMap<>();
private final long debounceMillis;
private final IEventManager delegate;
public DebouncedEventManager(long debounceMillis) {
this.debounceMillis = debounceMillis;
this.delegate = new InterfacedEventManager();
}
@Override
public void handle(GenericEvent event) {
Class<?> eventClass = event.getClass();
long now = System.currentTimeMillis();
Long last = lastEventTime.get(eventClass);
if (last == null || now - last >= debounceMillis) {
lastEventTime.put(eventClass, now);
delegate.handle(event);
}
}
// ... other methods
}
Event Logging
// Log all events for debugging
public class LoggingEventManager implements IEventManager {
private final IEventManager delegate = new InterfacedEventManager();
@Override
public void handle(GenericEvent event) {
System.out.println("[EVENT] " + event.getClass().getSimpleName());
delegate.handle(event);
}
// ... other methods
}