Skip to main content
JDA’s event system is flexible and allows you to implement custom event managers. This guide covers creating custom event managers for specialized event handling needs.

Event Manager Interface

The IEventManager 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 the EventListener 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

  1. Thread safety: Ensure your event manager is thread-safe, especially when managing listener lists
  2. Error handling: Always catch and handle exceptions in event handlers to prevent crashes
  3. Performance: Avoid heavy processing in the handle() method; delegate to thread pools if needed
  4. Documentation: Clearly document your event manager’s behavior and requirements
  5. Testing: Thoroughly test your event manager with various event types and listener configurations
  6. 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
}

Build docs developers (and LLMs) love