Event Handler Registration in Java I have a stateful object on which I'd like client code to be able to register many event handlers. These handlers have different signatures—they usually accept the originating object and some piece of data relevant to the event.

public class StatefulObject {
  @FunctionalInterface
  private interface Callback {
    void call(Object... args);
  }

  private enum Event {PLACEMENT, FILL, CANCEL, ERROR /* etc... */}
  private final Map&lt;Event, List&lt;Callback&gt;&gt; callbacks = new HashMap&lt;&gt;();

  public StatefulObject onPlacement(Consumer&lt;StatefulObject&gt; placementCallback) {
    callbacksFor(Event.PLACEMENT).add(args -&gt; placementCallback.accept((StatefulObject) args[0]));
    return this;
  }

  public StatefulObject onError(BiConsumer&lt;StatefulObject, Exception&gt; errorCallback) {
    callbacksFor(Event.ERROR).add(args -&gt; errorCallback.accept((StatefulObject) args[0], (Exception) args[1]));
    return this;
  }

  // ... etc

  private List&lt;Callback&gt; callbacksFor(Event event) {
    List&lt;Callback&gt; callbacksList = callbacks.getOrDefault(event, new ArrayList&lt;&gt;());
    callbacks.put(Event.ERROR, callbacksList);
    return callbacksList;
  }
}


My idea here is that (i) I shouldn't have many Lists/Arrays of callbacks and (ii) I shouldn't have unchecked casts. Instead of declaring a Map&lt;Event, Object&gt; and then casting the returned Object to List&lt;BiConsumer&lt;StatefulObject, Exception&gt;&gt;, for example, I thought it would be better to have a generic Callback and then cast the individual arguments.

Is this pattern reasonable, or is it unclear? Access is obviously restricted to just this class.

Thanks for your opinions!
Can you elaborate on that a bit? I fell like I do have a common interface (Callback) which prevents the dispatching method from having to do anything outlandish. For instance if I catch an exception and want to send it to the onError callback it looks something like 

for (Callback cb : callbacksFor(Event.ERROR))
    cb.call(this, someException);. 

  
  That's the dispatching method, right?


Right.

Problem here is that you pass the current object (having a state an business logic) as data to the callback. But you should only sent data transfer objects (without business logic and immutable by any chance).

So the common interface of the callback should look like this:

interface Callback{
   enum Key {EXCEPTION, DATA_1,DATA_2,DATA_3, ... }
   void onError(Map&lt;Key, Object);
}


and you sample would change to:

Map&lt;Key, Object&gt; currentState = copyInstanceMembersToMap(); 
currentState.put(Key.EXCEPTION, someException);
for (Callback cb : callbacksFor(Event.ERROR))
    cb.call(currentState);


Then the callbacks themselves decide which data to use. 

And you can use the same callback interface for other listeners as well.

Unfortunately you loose the type safety for the values in that map. 
But you could get that back a little:

public abstract class GenericEnum {
    enum Key {
        EXCEPTION {
            @Override
            public &lt;Exception&gt; Exception getFrom(Map&lt;Key, Object&gt; map) {
                return (Exception) map.get(this);
            }
        },
        DATA_1 {
            @Override
            public &lt;String&gt; String getFrom(Map&lt;Key, Object&gt; map) {
                return (String) map.get(this);
            }
        },
        DATA_2 {
            @Override
            public &lt;File&gt; File getFrom(Map&lt;Key, Object&gt; map) {
                return (File) map.get(this);
            }
        };
        public abstract &lt;T&gt; T getFrom(Map&lt;Key, Object&gt; map);
    }

    public static void main(String[] args) {
        Map&lt;Key, Object&gt; testMap = new HashMap&lt;&gt;();
        testMap.put(Key.EXCEPTION, new java.lang.Exception());
        testMap.put(Key.DATA_1, "test");
        testMap.put(Key.DATA_2, new File(System.getProperty("user.home")));
        Exception value1 = Key.EXCEPTION.getFrom(testMap);
        String value2 = Key.DATA_1.getFrom(testMap);
        File from = Key.DATA_2.getFrom(testMap);
        String value3 = Key.DATA_2.getFrom(testMap); // ClassCastException

    }

}