Events & Handlers

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against GWT 2.13.1 (2026-06-19), gwt-maven-plugin 1.3.0 (net.ltgt.gwt.maven), JDK 21 running the compiler and the tests with -sourceLevel 17, and the Jakarta Servlet 6 server of the Bookshelf scenario, checked on 2026-10-08. Events and handlers are client code: everything here is compiled to JavaScript and runs in the browser. The event classes that carry no widget dependency (com.google.web.bindery.event.shared) may also be used in shared code and on the server, because they only need the emulated JRE. There is nothing to configure in the build: the handler types are part of com.google.gwt.user.User, and the event bus is part of the same module. Every example was compiled with the 2.13.1 compiler and run twice: the DOM, bus and lifecycle examples in HtmlUnit through GWTTestCase (with assertions enabled), and the window, drag-and-drop, pointer and keyboard examples in headless Google Chrome 155 driven through the DevTools protocol. The outputs and messages are quoted as they were printed.

A browser application is a program that waits. Almost everything it does is a reaction to something that happened: the user clicked, a key went down, the window was resized, a server answered, another part of the application changed the state. GWT turns every one of these into the same shape: an event object, a handler interface with one method per event, and a registration that proves a handler was added and lets you remove it again. This page covers that shape from the browser’s click all the way to events that exist only inside the Bookshelf application.

One model, four sources of events

The same handler pattern serves four different sources of events. Knowing which source an event comes from tells you who dispatches it, whether the browser can be told to ignore it, and how long a registration lives.

Table 1. The four sources of events
Source Examples Dispatched by Registered with

DOM events of one element

ClickEvent, KeyDownEvent, DragStartEvent, ScrollEvent

the browser, then Widget.onBrowserEvent and DomEvent.fireNativeEvent

addClickHandler(...), addDomHandler(handler, type), addBitlessDomHandler(handler, type)

Logical (widget) events

ValueChangeEvent, SelectionEvent, OpenEvent, CloseEvent, AttachEvent

the widget itself, when its state changes

addValueChangeHandler(...), addSelectionHandler(...), addAttachHandler(...)

Global browser events

native preview of every event, Window resize, closing and scroll

Event and Window, which keep static handler lists

Event.addNativePreviewHandler(...), Window.addResizeHandler(...)

Application events

a custom LoanChangedEvent or BookSelectedEvent

your own code, through an event bus or a HandlerManager

eventBus.addHandler(type, handler)

All four use the same two supertypes. com.google.web.bindery.event.shared.Event<H> is the base class of every event: it has an Event.Type<H> key, a source and a protected dispatch(H) method. com.google.gwt.event.shared.GwtEvent<H extends EventHandler> extends it and adds a liveness check; all the stock GWT events extend GwtEvent. The marker interface EventHandler (com.google.gwt.event.shared.EventHandler) is implemented by every stock handler interface.

The path of a DOM event through a widget is the one worth keeping in mind, because it explains the ordering of everything else on this page.

sequenceDiagram participant B as Browser participant D as GWT dispatcher on the element participant P as Native preview handlers participant W as Widget.onBrowserEvent participant M as Widget handler manager participant H as Your ClickHandler B->>D: click on a sunk element D->>P: fire, last registered preview first P-->>D: not cancelled D->>W: onBrowserEvent(event) Note over W: your override runs first, then super W->>M: DomEvent.fireNativeEvent(event, this, element) M->>H: onClick(ClickEvent) in registration order H-->>M: returns, or throws Note over M: exceptions are collected and rethrown as an UmbrellaException

The element only sends an event to GWT if the widget has sunk it (sinkEvents for events that have a bit, sinkBitlessEvent for the others). The helper methods addClickHandler, addDomHandler and addBitlessDomHandler do the sinking for you. The bit constants and the lifecycle (what attach and detach do to a sunk event) are documented on the DOM & Elemental2 page; this page builds on them.

Handlers, events and registrations

Every stock event comes as a pair: a handler interface with one method named on plus the event, and an event class with a static getType(). The interface for ClickEvent is ClickHandler with void onClick(ClickEvent event); for KeyDownEvent it is KeyDownHandler with void onKeyDown(KeyDownEvent event). A widget that can produce an event implements the matching Has...Handlers interface, whose single method is add...Handler (HasClickHandlers.addClickHandler(ClickHandler)), and the call returns a HandlerRegistration.

The Developer Guide builds on this pattern with two examples: an anonymous class per widget, and one handler instance shared by several widgets that tells them apart with event.getSource(). Because every stock handler interface has exactly one method, a lambda works as well (the Bookshelf module compiles with -sourceLevel 17), and the shared-handler form stays useful when many widgets need the same behaviour:

One handler instance for two buttons, and removing it (HtmlUnit run)
Button b1 = new Button("One");
Button b2 = new Button("Two");
RootPanel.get().add(b1);
RootPanel.get().add(b2);

ClickHandler shared = e -> log.add("click from " + ((Button) e.getSource()).getText()
    + " at " + e.getClientX() + "," + e.getClientY() + " type=" + e.getNativeEvent().getType());

com.google.gwt.event.shared.HandlerRegistration r1 = b1.addClickHandler(shared);
b2.addClickHandler(shared);

b1.getElement().dispatchEvent(click());   // click() = Document.get().createClickEvent(1, 0, 0, 12, 34, ...)
b2.getElement().dispatchEvent(click());
r1.removeHandler();
r1.removeHandler();                        // removing twice is harmless
b1.getElement().dispatchEvent(click());    // nobody listens any more
click from One at 12,34 type=click
click from Two at 12,34 type=click

The third dispatch printed nothing, and the second removeHandler() did not throw.

HandlerRegistration and removing handlers

HandlerRegistration has one method, removeHandler(). There are two interfaces with that name, and they are related: com.google.web.bindery.event.shared.HandlerRegistration is the base, and com.google.gwt.event.shared.HandlerRegistration extends it without adding anything. Widget methods return the GWT one and the bus methods return the bindery one, so code that stores both kinds in one field should use the bindery interface.

Table 2. Facts about registrations (read from the 2.13.1 sources and exercised)
Fact Detail

Handlers are held until removed

Nothing removes a handler when the widget is detached; the registration is the only handle. A handler held by a long-lived object (the Window, the preview stack, an application bus) keeps the handler and everything it captures reachable.

Order

Handlers of one type on one source run in registration order. Native preview handlers are the exception and run in reverse order (see below).

Removal while the event is being fired

On a SimpleEventBus a handler can remove itself or another handler from inside a handler; the removal is queued until the outermost fireEvent returns (measured with a handler that removes itself: it ran once and not again).

Removing twice

Harmless: the probe called removeHandler() twice on a widget registration, and SimpleEventBus ignores a handler that is no longer in its list.

Combining

HandlerRegistrations.compose(HandlerRegistration...) returns one registration that removes all of them (added in the 2.6 line, see "What changed recently").

A typical use is a widget that registers several handlers on children and wants to release them with one call from onUnload:

private HandlerRegistration registrations;

@Override
protected void onLoad() {
  registrations = HandlerRegistrations.compose(
      saveButton.addClickHandler(e -> save()),
      title.addValueChangeHandler(e -> markDirty()),
      Window.addWindowClosingHandler(e -> warnIfDirty(e)));
}

@Override
protected void onUnload() {
  registrations.removeHandler();
}

The registrations of handlers added to the widget’s own children need not be removed this way when the children are discarded with the widget: a handler registered on a button is held by the button’s handler manager, so it goes away with the button. The ones that must be removed are those held by someone who outlives the widget, which the section "Handler lifetime and leaks" below measures.

The DOM events

GWT wraps the browser events that matter for application widgets in com.google.gwt.event.dom.client. Each one has a native event name (the constants live in com.google.gwt.dom.client.BrowserEvents), a handler, an event class and, on FocusWidget and the other widgets that support them, an add...Handler method.

Table 3. DOM event types of com.google.gwt.event.dom.client (2.13.1)
Native event Handler Event class Widget method

click

ClickHandler

ClickEvent

addClickHandler (HasClickHandlers)

dblclick

DoubleClickHandler

DoubleClickEvent

addDoubleClickHandler

contextmenu

ContextMenuHandler

ContextMenuEvent

addDomHandler(handler, ContextMenuEvent.getType()) (no stock widget implements HasContextMenuHandlers)

mousedown, mouseup, mousemove, mouseover, mouseout

MouseDownHandler, MouseUpHandler, MouseMoveHandler, MouseOverHandler, MouseOutHandler

MouseDownEvent, …​

addMouseDownHandler, …​ (HasAllMouseHandlers groups them)

wheel (see below)

MouseWheelHandler

MouseWheelEvent

addMouseWheelHandler

keydown, keypress, keyup

KeyDownHandler, KeyPressHandler, KeyUpHandler

KeyDownEvent, KeyPressEvent, KeyUpEvent

addKeyDownHandler, …​ (HasAllKeyHandlers)

focus, blur

FocusHandler, BlurHandler

FocusEvent, BlurEvent

addFocusHandler, addBlurHandler (HasAllFocusHandlers)

change

ChangeHandler

ChangeEvent

addChangeHandler (HasChangeHandlers: ListBox, FileUpload, the text boxes)

scroll

ScrollHandler

ScrollEvent

addScrollHandler (HasScrollHandlers: scroll panels through HasScrolling, the native scrollbars)

load, error

LoadHandler, ErrorHandler

LoadEvent, ErrorEvent

addLoadHandler, addErrorHandler (Image; Frame has addLoadHandler)

touchstart, touchmove, touchend, touchcancel

TouchStartHandler, …​

TouchStartEvent, …​

addTouchStartHandler, …​ (HasAllTouchHandlers)

gesturestart, gesturechange, gestureend

GestureStartHandler, …​

GestureStartEvent, …​

addGestureStartHandler, …​ (HasAllGestureHandlers)

dragstart, drag, dragend, dragenter, dragover, dragleave, drop

DragStartHandler, DragHandler, DragEndHandler, DragEnterHandler, DragOverHandler, DragLeaveHandler, DropHandler

DragStartEvent, …​

addDragStartHandler, …​ (HasAllDragAndDropHandlers)

losecapture

LoseCaptureHandler

LoseCaptureEvent

addDomHandler(handler, LoseCaptureEvent.getType()) (no stock widget implements HasLoseCaptureHandlers)

progress, ended, canplaythrough, loadedmetadata

ProgressHandler, EndedHandler, CanPlayThroughHandler, LoadedMetadataHandler

the matching ...Event

HasAllMediaHandlers (audio and video widgets)

Three things are missing from the table on purpose. There is no handler type for pointer events (pointerdown, pointermove) and none for input; the section "Events GWT does not know" shows how to add them. FocusWidget (buttons, text boxes, list boxes) supplies the click, double-click, key, mouse, focus, touch, gesture and drag-and-drop methods; other widgets implement the Has...Handlers interfaces that fit them, and a bare Widget supplies only addDomHandler and addHandler, which is what the next section uses. And the wheel row needs a remark: the 2.12.0 release notes say that the browser’s mousewheel event is deprecated and that GWT’s use of it was replaced by the standard wheel event, "transparent to applications", so MouseWheelHandler keeps working.

The helper interfaces make it easy to listen to a whole family with one object. HandlesAllKeyEvents, HandlesAllMouseEvents, HandlesAllFocusEvents and HandlesAllTouchEvents are abstract classes that implement all handlers of a family; you extend one and call addKeyHandlersTo(source) (or the equivalent method of the other classes). That method returns void: it registers the three handlers and drops their registrations, so it suits handlers that live as long as the source. When you need to remove them, add the three handlers yourself and combine the registrations with HandlerRegistrations.compose:

HandlesAllKeyEvents keys = new HandlesAllKeyEvents() {
  @Override public void onKeyDown(KeyDownEvent e) { track("down", e.getNativeKeyCode()); }
  @Override public void onKeyUp(KeyUpEvent e)     { track("up", e.getNativeKeyCode()); }
  @Override public void onKeyPress(KeyPressEvent e) { track("char", e.getCharCode()); }
};
keys.addKeyHandlersTo(searchBox);   // searchBox implements HasAllKeyHandlers, but nothing to remove later

HandlerRegistration removable = HandlerRegistrations.compose(
    searchBox.addKeyDownHandler(keys),
    searchBox.addKeyUpHandler(keys),
    searchBox.addKeyPressHandler(keys));

The event object

A DomEvent wraps the browser’s event. getNativeEvent() returns the NativeEvent overlay, whose accessors are the ones that matter in practice:

Table 4. Accessors of NativeEvent and its GWT wrappers
API What it gives

getType(), getEventTarget(), getCurrentEventTarget(), getRelatedEventTarget()

the event name; the element that was hit; the element whose listener is running; the other element in mouseover/mouseout/focus. getEventTarget() returns an EventTarget, so convert with Element.as(...) before using it as an element

getClientX(), getClientY(), getScreenX(), getScreenY()

coordinates in the viewport and on the screen. MouseEvent.getX()/getY() give coordinates relative to the element the handler was added to, and getRelativeX(Element)/getRelativeY(Element) relative to any element

getButton(), MouseEvent.getNativeButton()

the mouse button; compare with the NativeEvent.BUTTON_LEFT, BUTTON_MIDDLE and BUTTON_RIGHT constants

getKeyCode(), KeyCodeEvent.getNativeKeyCode(), KeyPressEvent.getCharCode()

the key code of keydown/keyup and the typed character of keypress. KeyCodes holds the constants (KEY_ENTER is 13, KEY_ESCAPE is 27), and isArrowKey(int)

getCtrlKey(), getShiftKey(), getAltKey(), getMetaKey()

modifier keys. The GWT wrappers add isControlKeyDown(), isShiftKeyDown(), isAltKeyDown(), isMetaKeyDown() and, on key events, isAnyModifierKeyDown()

getTouches(), getTargetTouches(), getChangedTouches()

the Touch lists of touch events

getDeltaX(), getDeltaY()

the wheel deltas; getMouseWheelVelocityY() is the older integer form

getDataTransfer()

the DataTransfer object of a drag event (next sections)

preventDefault(), stopPropagation()

cancel the browser’s default action, stop the event from bubbling (also on DomEvent itself)

Two properties of DomEvent are easy to get wrong.

  • Events are flyweights. Each DomEvent.Type holds one event instance that is reused for every dispatch of that type. DomEvent.fireNativeEvent stores the current native event in it, fires it, and puts the previous value back. A handler therefore must not keep the ClickEvent for later; copy what is needed (getClientX(), the target element).

  • Stock events are accessed on the same call stack only. GwtEvent.getSource() and the DomEvent accessors call an assertion that the event is still live (the exact message is in the pitfalls table).

Key events

keydown and keyup carry the key code of the physical key; keypress carries the character that the key produced and exists only for keys that produce one. Use keydown for commands (Enter, Escape, arrows, shortcuts) and keypress for filtering typed characters. The Bookshelf search box accepts digits only for a "jump to book number" field like this:

jump.addKeyPressHandler(e -> {
  char c = e.getCharCode();
  if (!Character.isDigit(c) && !e.isAnyModifierKeyDown()) {
    jump.cancelKey();           // ValueBoxBase.cancelKey() calls preventDefault on the key event
  }
});

The check is a convenience for the user, never a validation: anything the user types, pastes or drops can bypass a key filter, and the server (or a ValueChangeHandler plus the validation framework) must reject bad values again.

Logical events

The package com.google.gwt.event.logical.shared holds events that say what changed instead of how the user changed it. They are fired by the widget, and the same event may come from a click, a key or a call from code.

Table 5. Logical events of com.google.gwt.event.logical.shared
Event and handler Typical source and meaning

ValueChangeEvent<T>, ValueChangeHandler<T>

HasValue<T> widgets (TextBox, CheckBox, ListBox, DateBox) and History. getValue() returns the new value

SelectionEvent<I>, SelectionHandler<I>; BeforeSelectionEvent<I>, BeforeSelectionHandler<I>

tab and tree selection. The before-event can be cancelled with cancel()

OpenEvent<T>, CloseEvent<T> with OpenHandler<T>, CloseHandler<T>

open and close of tree items, disclosure panels and popups. PopupPanel fires CloseEvent with isAutoClosed()

ResizeEvent, ResizeHandler

the browser window (Window.addResizeHandler)

AttachEvent, HasAttachHandlers

every widget, when it is attached or detached (Widget.addAttachHandler)

HighlightEvent, ShowRangeEvent, InitializeEvent

date pickers and rich text editors

ValueChangeEvent has a rule worth knowing, measured on a TextBox: setValue(value, true) fires the event only when the value actually differs from the current one (ValueChangeEvent.fireIfNotEqual), while the native change event fires it unconditionally (ValueChangeEvent.fire).

TextBox box = new TextBox();
RootPanel.get().add(box);
box.addValueChangeHandler(e -> log.add("value -> " + e.getValue()));

box.setValue("a");                  // fireEvents is false: silent
box.setValue("b", true);            // changed: fires
box.setValue("b", true);            // same value: silent
box.setValue("c", true);            // changed: fires
box.getElement().dispatchEvent(Document.get().createChangeEvent());   // native change: fires again
value -> b
value -> c
value -> c

So code that listens to a form field should be idempotent: a ValueChangeEvent does not prove that the value is different from the last one you saw.

sinkEvents, onBrowserEvent and custom DOM events

The stock widgets call sinkEvents for you. A widget you build directly on a DOM element does it itself, then handles the events in onBrowserEvent(Event). The DOM & Elemental2 page shows the mechanism with a click counter; here the Bookshelf BookRow uses it the way a real widget does: it turns browser events into application events and still lets users add ordinary handlers.

The Bookshelf BookRow widget (src/main/java/com/example/bookshelf/client/events/BookRow.java)
public class BookRow extends Widget implements HasDoubleClickHandlers {
  private final long bookId;
  private final EventBus bus;                          // com.google.web.bindery.event.shared.EventBus

  public BookRow(long bookId, String title, EventBus bus) {
    this.bookId = bookId;
    this.bus = bus;
    setElement(Document.get().createDivElement());
    getElement().setTabIndex(0);
    getElement().setInnerText(title);                  // text, never HTML, for data
    sinkEvents(Event.ONCLICK | Event.ONKEYDOWN);       // the bits handled in onBrowserEvent
  }

  @Override
  public void onBrowserEvent(Event event) {
    switch (event.getTypeInt()) {
      case Event.ONCLICK:
        bus.fireEvent(new BookSelectedEvent(bookId));
        break;
      case Event.ONKEYDOWN:
        if (event.getKeyCode() == KeyCodes.KEY_ENTER) {
          event.preventDefault();                      // stop the browser's own Enter behaviour
          bus.fireEvent(new BookSelectedEvent(bookId));
        }
        break;
      default:
        break;
    }
    super.onBrowserEvent(event);                       // runs the handlers added with addDomHandler
  }

  @Override
  public HandlerRegistration addDoubleClickHandler(DoubleClickHandler handler) {
    return addDomHandler(handler, DoubleClickEvent.getType());   // sinks dblclick for us
  }
}

The order in which things happen is the point of the example. The widget was attached to the page with two preview handlers registered, then a click and an Enter key were dispatched at its element. The preview handlers run first (the later one before the earlier one), then onBrowserEvent, and the handlers added with addDomHandler run last, from super.onBrowserEvent:

preview 2: click first=true
preview 1: click first=false
bus: book 7 selected
click handler (addDomHandler)
preview 2: keydown first=true
preview 1: keydown first=false
bus: book 7 selected

In headless Chrome 155 the same widget received a real mouse click and a real Enter key (sent through the DevTools protocol) and a synthetic dblclick dispatched at the element:

ready
bus: book 7 selected
bus: book 7 selected
dblclick at 40,50

The first bus: line is the click, the second is the Enter key.

addDomHandler or addHandler

A widget has two registration methods that look alike and behave differently:

Table 6. Widget registration methods
Method Behaviour

addDomHandler(handler, DomEvent.Type)

registers the handler and sinks the native event, using sinkEvents when the event has a bit and sinkBitlessEvent when it has none. Use it for DOM events

addBitlessDomHandler(handler, DomEvent.Type)

registers the handler and always sinks the event by name; for events that have no bit (added in the 2.6 line)

addHandler(handler, GwtEvent.Type)

registers the handler on the widget’s handler manager and does nothing else. Use it for events that the widget (or you) fire with fireEvent: logical events and custom events

Using addHandler for a DOM event type compiles and then never fires, because nobody asked the browser to send the event. The probe added a click handler with addHandler and dispatched a click: nothing; it then added a second one with addDomHandler and dispatched again, and both handlers ran, in registration order:

-- after addHandler
addHandler click
addDomHandler click

Events GWT does not know

GWT has no handler type for pointer events or for the input event. The release notes of the 2.6 line added Widget.addBitlessDomHandler() for this: "DOM events not known to GWT can handled using Widget.addBitlessDomHandler()", which lets third-party code handle events GWT itself does not know about. You supply the event class; its Type registers the native event name together with a flyweight instance:

A pointerdown event (PointerDownEvent.java)
public class PointerDownEvent extends DomEvent<PointerDownEvent.Handler> {

  public interface Handler extends EventHandler {
    void onPointerDown(PointerDownEvent event);
  }

  private static final Type<Handler> TYPE = new Type<>("pointerdown", new PointerDownEvent());

  public static Type<Handler> getType() { return TYPE; }

  private PointerDownEvent() { }

  @Override
  public Type<Handler> getAssociatedType() { return TYPE; }

  @Override
  protected void dispatch(Handler handler) { handler.onPointerDown(this); }
}

// use it
row.addBitlessDomHandler(
    e -> log("pointerdown " + e.getNativeEvent().getType() + " at " + e.getNativeEvent().getClientX()),
    PointerDownEvent.getType());

A real mouse press in Chrome 155 printed pointerdown pointerdown at 119 immediately before the click’s bus: line, because the browser fires pointerdown before mousedown and click. When pointer or other newer APIs are needed in volume, Elemental2 (elemental2.dom, covered on the DOM & Elemental2 page) gives typed access to them, with addEventListener and a registration that you remove yourself.

mouseover and mouseout are filtered

Widget.onBrowserEvent contains one special case. For mouseover it fires the event only when the mouse comes from outside the widget, and for mouseout only when it leaves the widget: moving between child elements inside the same widget does not produce a stream of over and out events. The filter compares getRelatedEventTarget() with the widget’s element (isOrHasChild). A widget that overrides onBrowserEvent and sees raw mouseover events for children must do its own check, as the stock Widget does.

Native preview: Event.addNativePreviewHandler

Event.addNativePreviewHandler(NativePreviewHandler) registers a global handler that sees every native event GWT dispatches, before the target widget does. It is the tool for application-wide behaviour that does not belong to any widget: keyboard shortcuts, "click outside closes the popup", logging, a busy overlay that swallows input. The Javadoc spells out its rules, and the probe confirmed them:

  • The handler receives all native events, including events that arrive by bubbling, whereas normal handlers get only the events their widget sank.

  • Preview handlers fire in reverse order: the last one registered runs first. NativePreviewEvent.isFirstHandler() is true for the first handler called.

  • cancel() cancels the event: it is not delivered to the widget. consume() marks the event as handled and overrides a cancel, so the event still reaches the widget (the return value of the dispatch is !(isCanceled() && !isConsumed())).

  • The result is HandlerRegistration, and you must remove it. The Javadoc also warns that more than one GWT application on the same page makes the behaviour non-deterministic.

Two preview handlers; the inner one cancels a click (HtmlUnit run)
Button b = new Button("Cancelable");
RootPanel.get().add(b);
b.addClickHandler(e -> log.add("button received click"));

HandlerRegistration first = Event.addNativePreviewHandler(p -> {
  if ("click".equals(p.getNativeEvent().getType())) {
    log.add("outer preview sees click, canceled=" + p.isCanceled());
  }
});
HandlerRegistration second = Event.addNativePreviewHandler(p -> {
  if ("click".equals(p.getNativeEvent().getType())) {
    p.cancel();
    log.add("inner preview canceled it");
  }
});
b.getElement().dispatchEvent(click());
second.removeHandler();
first.removeHandler();
inner preview canceled it
outer preview sees click, canceled=true

The inner (later) handler ran first, the outer one saw the cancelled state, and the button never logged button received click: the click was cancelled before it reached the widget. The Developer Guide’s keyboard-shortcut example, which uses consume() to mark a shortcut as handled while letting it through, is quoted on the DOM & Elemental2 page; the older Event.addEventPreview(EventPreview) it replaced is deprecated in 2.13.1.

A shortcut for the Bookshelf catalogue ("press / to focus the search box", ignored while typing in a field) looks like this:

HandlerRegistration shortcut = Event.addNativePreviewHandler(p -> {
  if (p.getTypeInt() != Event.ONKEYPRESS) {
    return;
  }
  NativeEvent ne = p.getNativeEvent();
  Element target = Element.as(ne.getEventTarget());
  String tag = target.getTagName().toLowerCase();
  boolean typing = "input".equals(tag) || "textarea".equals(tag) || "select".equals(tag);
  if (!typing && ne.getCharCode() == '/' && !ne.getCtrlKey() && !ne.getAltKey()) {
    ne.preventDefault();               // keypress carries the character: do not type the slash into the box
    searchBox.setFocus(true);
    p.consume();
  }
});

The shortcut must be removed when the view that installed it goes away (shortcut.removeHandler() in onUnload), or it keeps focusing a box that is no longer on the page.

Window events

com.google.gwt.user.client.Window keeps the handlers for the browser window. The first handler of each kind installs the native listener; the registration removes your handler.

Table 7. Window events and handlers (2.13.1)
Method Handler and event Notes

Window.addResizeHandler(ResizeHandler)

ResizeHandler, ResizeEvent (getWidth(), getHeight())

the client size; GWT filters duplicate events when the size did not change

Window.addWindowClosingHandler(Window.ClosingHandler)

Window.ClosingHandler.onWindowClosing(Window.ClosingEvent)

fired before the page unloads (beforeunload); setMessage(String) asks the browser to confirm. If several handlers set a message the last one wins

Window.addWindowScrollHandler(Window.ScrollHandler)

Window.ScrollHandler, Window.ScrollEvent (getScrollLeft(), getScrollTop())

scrolling of the page

Window.addCloseHandler(CloseHandler<Window>)

CloseEvent<Window>

deprecated (needs the unload event, which browsers deprecated); use the closing handler and server-side session timeouts

The old addWindowCloseListener, addWindowResizeListener and addWindowScrollListener are deprecated and are wrappers around the handler API. The 2.12.0 release notes record the reason for the close handler: the unload event is deprecated in browsers, so the calls that need it are deprecated and the notes of each deprecated method say what to use instead.

Resize, closing and scroll handlers with the Bookshelf review editor
HandlerRegistration resize = Window.addResizeHandler(e ->
    layoutDebouncer.schedule(150));            // a Timer, see the delayed logic page

HandlerRegistration closing = Window.addWindowClosingHandler(e -> {
  if (reviewEditor.isDirty()) {
    e.setMessage("Unsaved review");            // modern browsers show a fixed text instead
  }
});

HandlerRegistration scroll = Window.addWindowScrollHandler(e ->
    backToTop.setVisible(e.getScrollTop() > 400));

The probe module registered a resize handler and a closing handler and drove headless Chrome 155 through the DevTools protocol. Resizing the viewport to 800x600 produced exactly one resize 800x600 line, although the size override was applied twice, and a synthetic resize event dispatched afterwards with the size unchanged produced nothing: GWT’s own filter (the source comment says that "on webkit and IE we sometimes get duplicate window resize events") drops events whose size did not change. Navigating away after real clicks opened the browser’s beforeunload dialog, and the closing handler had run (closing was logged), but the dialog’s message was the empty string: the text passed to setMessage is not shown by current browsers. MDN adds that browsers may skip the dialog for pages the user never interacted with. Use the handler to guard against losing unsaved work, and treat the message as a hint, not as UI.

A resize handler can run dozens of times per second while the user drags the window edge. Do cheap work in it, or debounce with a Timer or Scheduler.scheduleDeferred as the Delayed Logic & Scheduler page describes. Layout panels do not need a window handler: they resize their children through RequiresResize when the root layout panel resizes (see Panels & Layout).

Preventing defaults and stopping propagation

Two different things can be stopped, and they are easy to confuse.

Table 8. preventDefault versus stopPropagation
Call What it stops Typical use

preventDefault() (on DomEvent or NativeEvent)

the browser’s own action for the event: following a link, submitting a form, typing the character, opening the context menu, starting a text selection

Enter on a custom widget; a custom context menu (ContextMenuEvent); filtering key presses; accepting a drop

stopPropagation()

the bubbling of the event to the parent elements, so that handlers on ancestors do not see it

a nested control inside a clickable row

NativePreviewEvent.cancel()

delivery of the event to GWT widgets (global, before any widget)

modal overlays and global shortcuts

The distinction shows in a panel that contains a button, with a click handler on each. The probe dispatched a click at the button, first with the two handlers, then after adding a second button handler that stops propagation:

FlowPanel panel = new FlowPanel();
Button b = new Button("Inner");
panel.add(b);
RootPanel.get().add(panel);
panel.addDomHandler(e -> log.add("panel click, target is the button: "
    + (Element.as(e.getNativeEvent().getEventTarget()) == b.getElement())), ClickEvent.getType());
b.addClickHandler(e -> log.add("button click"));

b.getElement().dispatchEvent(click());
log.add("-- with stopPropagation");
b.addClickHandler(e -> { e.stopPropagation(); log.add("button stops it"); });
b.getElement().dispatchEvent(click());
button click
panel click, target is the button: true
-- with stopPropagation
button click
button stops it

The panel saw the click through bubbling, and getEventTarget() still named the button, so a handler on a container can tell which child was hit. After stopPropagation() the panel handler no longer ran. Note that stopPropagation() does not stop the other handlers registered on the same widget: button click ran before button stops it and would still run if it were registered after.

Do not use stopPropagation() to prevent a navigation or a form submit: that is preventDefault(). And do not call either on events that you only observe, such as the focus and blur of a form: cancelling them has no useful effect and hides the real behaviour from other handlers.

Drag and drop

Browsers have a native drag-and-drop model (the HTML Standard’s "drag-and-drop processing model"), and GWT exposes it. It replaced the approach of the 2007 book, which had no native support to build on and therefore assembles dragging from mouse listeners and mouse capture. That technique still works with the mouse events above, but it cannot carry files, links or text between applications and does not use the data and effect model of the platform.

The pieces in GWT 2.13.1:

  • Element.setDraggable(String) with Element.DRAGGABLE_TRUE, DRAGGABLE_FALSE or DRAGGABLE_AUTO sets the draggable attribute.

  • The seven events dragstart, drag, dragend (on the dragged element) and dragenter, dragover, dragleave, drop (on the target) have the handlers and events listed in the DOM-events table, all subclasses of DragDropEventBase. DragDropEventBase.isSupported() tells whether the browser supports them. FocusWidget (buttons, text boxes) has the add...Handler methods; other widgets use addDomHandler(handler, DragStartEvent.getType()).

  • DragDropEventBase.setData(format, data) and getData(format) are shortcuts for the DataTransfer object returned by getDataTransfer(). DataTransfer offers setData, getData, clearData, setDragImage(Element, x, y) and setDropEffect(DropEffect); DropEffect is an enum of the browser’s effects.

A draggable book card and a drop zone (EventsLab.java)
row.getElement().setDraggable(Element.DRAGGABLE_TRUE);
row.addDomHandler(e -> {
  e.setData("text", "book:7");            // what travels with the drag
  log("dragstart, data set");
}, DragStartEvent.getType());

Label zone = new Label("Drop here");
zone.addDomHandler(e -> {
  e.preventDefault();                     // marks the zone as a valid target
}, DragOverEvent.getType());
zone.addDomHandler(e -> {
  e.preventDefault();                     // do not let the browser navigate to the dropped data
  log("drop: " + e.getData("text"));
}, DropEvent.getType());

In headless Chrome 155 the probe intercepted a real drag started with the mouse (Input.setInterceptDrags), replayed it as dragEnter, dragOver and drop events at the zone, and printed:

dragstart, data set
drop: book:7

The rule that surprises most newcomers is in the HTML Standard and on MDN: an element is a valid drop target only if its dragover (and dragenter) event is cancelled with preventDefault(). That is why the zone above has a DragOverEvent handler that does nothing but cancel. The probe replayed events through the DevTools protocol, which does not exercise that rule, so the "no preventDefault, no drop" behaviour is quoted from the standard and was not reproduced here.

Two more cautions. A drop delivers data from outside the page (another tab, a file manager): getData(...) must be treated like any other user input and shown with setText, never setHTML. And HtmlUnit does not model drag data: a synthetic dragstart created with Document.createHtmlEvent("dragstart", true, true) reaches the handler with a null DataTransfer, so a handler that calls setData fails in GWTTestCase with Cannot call method "setData" of null even though it works in a real browser. Test the data flow in a browser run, or put it behind an interface that a plain-JUnit presenter test can fake.

Custom events and the event bus

Application events are the same pattern with your own classes. You need an event class, a handler interface and a static Type, and then something to fire the event through: a widget (every Widget is a HasHandlers), a HandlerManager, or an event bus.

Writing an event

Two base classes are available. GwtEvent<H extends EventHandler> is what the stock events use. com.google.web.bindery.event.shared.Event<H> is the same thing without the EventHandler marker, so the handler can be any interface, and it can be used in shared and server code. GwtEvent extends Event, so a GwtEvent can be fired through a bindery bus (the Bookshelf example below does it).

A GwtEvent: the stock pattern (LoanChangedEvent.java)
public class LoanChangedEvent extends GwtEvent<LoanChangedEvent.Handler> {

  /** The handler interface: one method per event, named after the event. */
  public interface Handler extends EventHandler {
    void onLoanChanged(LoanChangedEvent event);
  }

  /** One static Type per event class; the bus uses it as the key. */
  public static final Type<Handler> TYPE = new Type<>();

  private final long bookId;
  private final int copiesLeft;

  public LoanChangedEvent(long bookId, int copiesLeft) {
    this.bookId = bookId;
    this.copiesLeft = copiesLeft;
  }

  public long getBookId() { return bookId; }

  public int getCopiesLeft() { return copiesLeft; }

  @Override
  public Type<Handler> getAssociatedType() { return TYPE; }

  @Override
  protected void dispatch(Handler handler) { handler.onLoanChanged(this); }
}
The same pattern on the bindery base class (BookSelectedEvent.java)
public class BookSelectedEvent extends Event<BookSelectedEvent.Handler> {

  public interface Handler {
    void onBookSelected(BookSelectedEvent event);
  }

  public static final Type<Handler> TYPE = new Type<>();

  private final long bookId;

  public BookSelectedEvent(long bookId) { this.bookId = bookId; }

  public long getBookId() { return bookId; }

  @Override
  public Type<Handler> getAssociatedType() { return TYPE; }

  @Override
  protected void dispatch(Handler handler) { handler.onBookSelected(this); }
}

Rules for the class: give the Type a single static instance (it is the map key, and each new Type() gets its own identity number); keep the event immutable (final fields, no setters), because several handlers see the same instance; and put in it the data the handlers need, not references to widgets. The handler method is conventionally named on plus the event; the event class calls it from dispatch.

Which bus

Several classes with similar names exist because the event system was extracted from gwt-user into a smaller part (web.bindery) that the server and the RequestFactory can share.

Table 9. The event-bus classes
Class Package Role

EventBus (abstract), SimpleEventBus, ResettableEventBus

com.google.web.bindery.event.shared

the current API for new code: addHandler(type, handler), addHandlerToSource(type, source, handler), fireEvent(event), fireEventFromSource(event, source)

Event, Event.Type, HandlerRegistration, HandlerRegistrations, UmbrellaException

com.google.web.bindery.event.shared

the event base class, the key, the registration and the exception that collects handler failures

GwtEvent, EventHandler, HasHandlers, HandlerManager

com.google.gwt.event.shared

the GWT flavour used by widgets. HandlerManager is a per-source bus with a fixed source; Widget keeps one

EventBus, SimpleEventBus, ResettableEventBus (legacy flavour)

com.google.gwt.event.shared

compatibility wrappers over the bindery classes for code written before the move. The abstract EventBus throws UnsupportedOperationException ("Subclass responsibility. This class is a legacy wrapper for com.google.web.bindery.event.shared.EventBus. Use that directly, or try com.google.gwt.event.shared.SimpleEventBus") from the bindery signatures

Use com.google.web.bindery.event.shared.SimpleEventBus (typed as EventBus) for new code. The Activities and Places framework (see MVP, Activities & Places) takes a bindery EventBus in ActivityManager and PlaceController and hands each activity a ResettableEventBus that it resets when the activity stops, so activities that register handlers on it clean up for free.

What fireEvent does

The semantics below are read from the SimpleEventBus source and exercised in the probe. They are the implementation of 2.13.1; the Javadoc does not promise all of them.

SimpleEventBus bus = new SimpleEventBus();
Object shelfA = "shelf A";
Object shelfB = "shelf B";
bus.addHandler(BookSelectedEvent.TYPE, e -> log.add("global handler 1"));
bus.addHandlerToSource(BookSelectedEvent.TYPE, shelfA, e -> log.add("handler for shelf A"));
bus.addHandler(BookSelectedEvent.TYPE, e -> log.add("global handler 2"));

bus.fireEvent(new BookSelectedEvent(1));                    // no source
log.add("--");
bus.fireEventFromSource(new BookSelectedEvent(2), shelfA);  // source shelf A
log.add("--");
bus.fireEventFromSource(new BookSelectedEvent(3), shelfB);  // a source nobody registered for
global handler 1
global handler 2
--
handler for shelf A
global handler 1
global handler 2
--
global handler 1
global handler 2

Handlers for a specific source run before the global ones, and global handlers hear every event. A plain fireEvent (no source) reaches only the global handlers, because an event without a source cannot match a source-specific registration.

Table 10. SimpleEventBus behaviour (2.13.1)
Situation Behaviour

Order

registration order within a list. Source-specific handlers first, then the global ones

A handler throws

the other handlers still run. After the last one fireEvent throws com.google.web.bindery.event.shared.UmbrellaException carrying all the causes; its message is Exception caught: <message> for one failure and N exceptions caught: <m1>; <m2> for several

A handler is added while firing

the add is queued and applied when the outermost fireEvent returns, so it first sees the next event

A handler is removed while firing

queued the same way

Null arguments

NullPointerException with the messages in the pitfalls table

The "added while firing" rule was measured: a handler that registers a second handler during its first event did not cause that second one to run for the same event. The fourth fire printed late handler only on the second fireEvent:

-- add during fire
global handler 1
global handler 2
-- second fire
global handler 1
global handler 2
late handler

A handler that removes itself (a "once" handler) works the same way:

final HandlerRegistration[] self = new HandlerRegistration[1];
self[0] = bus.addHandler(BookSelectedEvent.TYPE, e -> { log.add("once"); self[0].removeHandler(); });
bus.addHandler(BookSelectedEvent.TYPE, e -> log.add("always"));
bus.fireEvent(new BookSelectedEvent(1));
bus.fireEvent(new BookSelectedEvent(1));
once
always
always

The failure behaviour is the one to design for. In the probe the first of two handlers threw IllegalStateException("first failed"):

second still ran
com.google.web.bindery.event.shared.UmbrellaException: Exception caught: first failed
cause: java.lang.IllegalStateException: first failed
causes: 1

An exception in a handler therefore never silences the handlers after it, but the failure still comes out of fireEvent, so the code that fires the event is the one that sees it. When the event comes from the browser, that code is GWT itself, and the exception reaches the uncaught-exception handler (GWT.setUncaughtExceptionHandler) as a com.google.gwt.event.shared.UmbrellaException: a click handler that threw IllegalStateException("review not saved") was reported by the test harness as com.google.gwt.event.shared.UmbrellaException: Exception caught: review not saved. Install an uncaught-exception handler that logs and reports (the Bookshelf App does), and keep handlers short and defensive.

ResettableEventBus

ResettableEventBus wraps another bus and remembers every registration made through it. removeHandlers() removes all of them from the wrapped bus, and only them. It is the right tool for a component with a lifetime shorter than the bus: a presenter, a view, an activity. Handlers that others registered on the same wrapped bus stay.

SimpleEventBus bus = new SimpleEventBus();
ResettableEventBus scoped = new ResettableEventBus(bus);
scoped.addHandler(BookSelectedEvent.TYPE, e -> log.add("scoped"));
scoped.addHandler(BookSelectedEvent.TYPE, e -> log.add("scoped 2"));
bus.addHandler(BookSelectedEvent.TYPE, e -> log.add("unscoped"));

bus.fireEvent(new BookSelectedEvent(1));
scoped.removeHandlers();
log.add("--");
bus.fireEvent(new BookSelectedEvent(1));
scoped
scoped 2
unscoped
--
unscoped

fireEvent on the scoped bus goes straight to the wrapped bus, so an event fired through it is heard by everyone.

Events fired by a widget: HandlerManager and dead events

A widget fires its own events with fireEvent(GwtEvent<?>), which goes through its HandlerManager. HandlerManager sets the source of the event to the widget for the duration of the dispatch and marks the event dead afterwards. A dead event refuses getSource(): the accessors call assertLive(). A handler that stores the event and uses it later fails when assertions are enabled (GWTTestCase runs with them; a production compile strips them unless -checkAssertions is used, see Compiler & Compilation), with:

source inside: <button type="button" class="gwt-Button">x</button>
AssertionError: This event has already finished being processed by its original handler manager, so you can no longer access it

Copy the data out of the event instead (event.getBookId() is a plain field read and is safe, but getSource() is not). Events fired directly through a bus are not killed this way, which is one more reason to keep bus events immutable and self-contained.

Declaring events on your own widget

A custom widget that fires logical events implements the Has...Handlers interface and uses addHandler:

public class RatingBar extends Composite implements HasValueChangeHandlers<Integer> {
  private int rating;

  public int getRating() { return rating; }

  public void setRating(int value, boolean fireEvents) {
    int old = this.rating;
    this.rating = value;
    render();
    if (fireEvents) {
      ValueChangeEvent.fireIfNotEqual(this, old, value);
    }
  }

  @Override
  public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Integer> handler) {
    return addHandler(handler, ValueChangeEvent.getType());   // not addDomHandler: not a DOM event
  }
  // initWidget(...), render() and the click handling are on the page about custom widgets
}

This is how the stock TextBox behaves, which is why the Editor framework, @UiHandler and the cell widgets can treat any HasValue the same way.

When to use a bus

An event bus decouples a sender from its receivers, and that is its cost as well. Anything can fire anything, receivers are found only by searching for the event type, and every forgotten removal is a leak. Use it for application-level facts that several unrelated components care about (LoanChangedEvent, a place change, a login state change). Prefer a direct call, a callback or a handler on the widget for relations between a parent and its child, and an interface on the presenter for talking to a view. The standard Activities and Places setup keeps one application bus and gives every activity a scoped one, which is the pattern of the example below.

The Bookshelf worked example

The Bookshelf catalogue shows each book as a BookRow, and a StockBadge next to it shows the copies left. Clicking a row (or pressing Enter on it) fires a BookSelectedEvent on the application bus. Borrowing or returning a copy fires a LoanChangedEvent; every badge that is on the page for that book updates, and a badge that has left the page does not.

sequenceDiagram participant U as User participant R as BookRow widget participant Bus as Application SimpleEventBus participant A as CatalogActivity participant S as StockBadge via ResettableEventBus U->>R: click or Enter R->>Bus: fireEvent(BookSelectedEvent) Bus->>A: onBookSelected(event) A->>A: load details through GWT-RPC A->>Bus: fireEvent(LoanChangedEvent) Bus->>S: onLoanChanged(event) S->>S: show new copies when the book id matches Note over S: onUnload calls removeHandlers on the scoped bus

The badge registers in onLoad and unregisters in onUnload, through a ResettableEventBus, so the lifetime of the registration is exactly the lifetime of the widget on the page:

The Bookshelf StockBadge (StockBadge.java)
public class StockBadge extends Composite {
  private final Label label = new Label();
  private final ResettableEventBus scoped;
  private final long bookId;

  public StockBadge(long bookId, int copies, EventBus appBus) {
    this.bookId = bookId;
    this.scoped = new ResettableEventBus(appBus);
    initWidget(label);
    show(copies);
  }

  @Override
  protected void onLoad() {
    scoped.addHandler(LoanChangedEvent.TYPE, e -> {
      if (e.getBookId() == bookId) {
        show(e.getCopiesLeft());
      }
    });
  }

  @Override
  protected void onUnload() {
    scoped.removeHandlers();           // every registration made through the scoped bus
  }

  private void show(int copies) {
    label.setText(copies + (copies == 1 ? " copy" : " copies"));
  }
}

The test drove the badge through three states: detached, attached, and detached again. Events for the badge’s book arrive only while it is attached:

SimpleEventBus bus = new SimpleEventBus();
StockBadge badge = new StockBadge(7, 3, bus);
bus.fireEvent(new LoanChangedEvent(7, 2));
log.add("detached badge: " + badge.getText());
RootPanel.get().add(badge);
bus.fireEvent(new LoanChangedEvent(7, 2));
bus.fireEvent(new LoanChangedEvent(8, 0));          // another book: ignored
log.add("attached badge: " + badge.getText());
RootPanel.get().remove(badge);
bus.fireEvent(new LoanChangedEvent(7, 1));
log.add("after detach: " + badge.getText());
detached badge: 3 copies
attached badge: 2 copies
after detach: 2 copies

The detached badge ignored the first event, the attached one showed 2 copies and ignored book 8, and after onUnload the badge no longer reacted to the event that would have set 1 copy. The server stays authoritative: the event only updates what the user sees, and the real number of copies comes back from the RPC call (with its XSRF protection) that the activity makes before it fires the event.

Handler lifetime and leaks

A handler added to a widget or its child is released with the widget. A handler added to something that outlives the widget (an application event bus, Window, Event.addNativePreviewHandler, a Timer, an Elemental2 listener on document) is released only by you. The DOM & Elemental2 page measures the same effect for global DOM listeners; the version for the event bus is just as direct. A label registered on the application bus in its constructor and never unregistered:

static class Leaky extends Label {
  Leaky(SimpleEventBus bus, List<String> log) {
    bus.addHandler(LoanChangedEvent.TYPE, e -> log.add("leaky label still handles " + e.getBookId()));
  }
}

SimpleEventBus bus = new SimpleEventBus();
Leaky l = new Leaky(bus, log);
RootPanel.get().add(l);
RootPanel.get().remove(l);                          // gone from the page
bus.fireEvent(new LoanChangedEvent(7, 1));
leaky label still handles 7

The label was removed from the page but is still alive and still handling events, because the bus holds the lambda and the lambda holds the label. The rules that keep applications clean:

  • register global and bus handlers in onLoad and remove them in onUnload (or use a ResettableEventBus and removeHandlers());

  • give each presenter or activity its own scoped bus;

  • keep the HandlerRegistration of anything global in a field;

  • never register in a constructor on something the widget does not own.

Widget.addAttachHandler(AttachEvent.Handler) lets code outside the widget react to attach and detach when overriding onLoad is not an option.

Migrating from listeners to handlers

The book (GWT 1.3 and 1.4, 2007) is built on listeners: interfaces with several methods, adapters, ListenerCollection helpers and addClickListener(...). Handlers replaced them in GWT 1.6, and the listener classes are still in gwt-user 2.13.1, all marked @Deprecated, so old code compiles with warnings. The advice of every @deprecated tag is to move to handlers, and the mapping is mechanical:

Table 11. Listeners and their replacements (from the @deprecated tags in the 2.13.1 sources)
Listener (deprecated) Replacement

ClickListener, addClickListener

ClickHandler, addClickHandler

KeyboardListener, KeyboardListenerAdapter

KeyDownHandler, KeyUpHandler, KeyPressHandler

FocusListener, FocusListenerAdapter

FocusHandler, BlurHandler

MouseListener, MouseListenerAdapter

MouseDownHandler, MouseUpHandler, MouseOverHandler, MouseMoveHandler, MouseOutHandler

MouseWheelListener

MouseWheelHandler

ChangeListener

ChangeHandler for DOM changes, ValueChangeHandler for logical value changes (they were separated in GWT 1.6)

LoadListener

LoadHandler and ErrorHandler

ScrollListener

ScrollHandler

PopupListener

CloseHandler

TabListener

addBeforeSelectionHandler and addSelectionHandler of TabPanel

TreeListener

SelectionHandler, OpenHandler, CloseHandler

TableListener

ClickHandler with HTMLTable.getCellForEvent(ClickEvent)

WindowResizeListener, WindowCloseListener, WindowScrollListener

Window.addResizeHandler, Window.addWindowClosingHandler, Window.addWindowScrollHandler

HistoryListener

History.addValueChangeHandler

EventPreview, DOM.addEventPreview

Event.addNativePreviewHandler

ClickListenerCollection and the other ...ListenerCollection classes

not needed: addDomHandler(handler, type) manages the handlers of your widget

What changes besides names:

  • The sender. A listener method received the sender widget (onClick(Widget sender)); a handler receives an event. Use event.getSource() for the widget, or capture it in a lambda, and note that the source of a non-widget event may not be a widget.

  • One method per event. A listener adapter existed so you could override one of five methods; a handler interface has one method, so a lambda or a small class does the job and no adapter is needed.

  • Removal. A listener was removed with removeClickListener(listener); a handler registration is removed with registration.removeHandler(). Storing the registration is the new duty.

  • Logical versus DOM. The old ChangeListener fired for user changes and for setText; the new ChangeHandler fires only for the browser’s change event. Code that depended on the old behaviour needs a ValueChangeHandler (and setValue(v, true) when it sets values from code).

  • Event preview. The old EventPreview returned a boolean from onEventPreview; the new handler calls cancel() or consume() on the event.

  • @UiHandler. In UiBinder templates, @UiHandler("save") on a method with a ClickEvent parameter registers the handler; the UiBinder page covers it.

A before-and-after for a Bookshelf confirmation button:

// 2007 style, deprecated in 2.13.1
save.addClickListener(new ClickListener() {
  public void onClick(Widget sender) {
    store();
  }
});

// handlers, with a lambda
HandlerRegistration reg = save.addClickHandler(event -> store());

// handlers, in a UiBinder owner class
@UiHandler("save")
void onSave(ClickEvent event) {
  store();
}

Security notes

  • Events carry untrusted input. The text of a KeyPressEvent, the value of a ValueChangeEvent and the data of a DropEvent come from the user or from another origin. Show them with setText/setInnerText or SafeHtml (see SafeHtml & XSS), never setHTML or setInnerHTML with concatenated data. BookRow uses setInnerText for that reason.

  • Client-side checks are a courtesy. A key filter, a disabled button or a "cancelled" click can all be bypassed with a direct request. Authorisation, validation and XSRF protection of the RPC and REST calls that the handlers start belong to the server (see XSRF, RPC & Server Security).

  • The closing prompt is not a security control. It can be skipped (a forced close, a crash), and browsers ignore its text.

  • Do not put secrets on the bus. Event objects are plain Java objects reachable by every handler, including libraries' handlers; carry ids and read the data from a service that checks the user’s rights.

  • Global handlers see everything. A native preview handler receives every key press on the page, including passwords typed into a field; it must never log or send the events it previews.

Pitfalls

The messages below were printed by the 2.13.1 run of the probes described at the top of the page (HtmlUnit through GWTTestCase with assertions enabled, unless marked as Chrome).

Symptom (messages quoted as printed) Cause Fix

A widget’s onBrowserEvent is never called; no error

sinkEvents was not called for that event, or was called with the wrong bit. The probe widget that overrides onBrowserEvent logged nothing for a click until sinkEvents(Event.ONCLICK) was called.

Call sinkEvents in the constructor, or use addDomHandler, which sinks for you.

A handler added with addHandler(handler, ClickEvent.getType()) never runs

addHandler registers on the handler manager only; it does not sink the DOM event. The probe printed nothing for the click until a handler was also added with addDomHandler.

Use addDomHandler for DOM events and addHandler only for events that are fired with fireEvent.

Handlers added with addDomHandler stop running after an onBrowserEvent override

the override did not call super.onBrowserEvent(event), which is what fires the handlers

Always call super.onBrowserEvent(event) (after your own code, or before it).

java.lang.AssertionError: This event has already finished being processed by its original handler manager, so you can no longer access it

a GwtEvent fired by a widget was stored and used later (getSource() calls assertLive())

Copy the needed values inside the handler. Do not keep events.

com.google.web.bindery.event.shared.UmbrellaException: Exception caught: first failed from fireEvent

a handler threw; the other handlers ran, and the failures are collected in one exception (N exceptions caught: ... for several). The widget variant is com.google.gwt.event.shared.UmbrellaException: Exception caught: review not saved

Fix the failing handler; catch UmbrellaException around fireEvent only where you must continue, and install GWT.setUncaughtExceptionHandler for the rest. Inspect getCauses().

java.lang.NullPointerException: Cannot add a null handler, Cannot add a handler with a null type, Cannot fire null event, Cannot add a handler with a null source, Cannot fire from a null source

the SimpleEventBus argument checks, one message per call

Pass non-null arguments. A source-specific registration needs a real source object.

java.lang.AssertionError: handler must not be null from addClickHandler(null) or addDomHandler(null, ...)

the widget API asserts its arguments (assertions on in development)

Do not register a null handler; with assertions off the null surfaces later as a dispatch error.

A handler added during fireEvent does not see the current event

SimpleEventBus queues adds and removes while it fires and applies them at the end (measured: late handler appeared on the second fire)

Do not rely on a new handler seeing the event that is being dispatched.

A removed widget keeps reacting to bus events (leaky label still handles 7)

a handler registered on an application bus in the constructor was never removed; the bus holds the lambda

Register in onLoad, remove in onUnload, or use a ResettableEventBus.

Two native preview handlers run in an unexpected order, or an event "disappears"

preview handlers run in reverse registration order, and cancel() stops delivery to the widget (unless consume() was called)

Order the registrations deliberately, check isFirstHandler(), and remove the handler when its view is gone.

ResizeEvent is not delivered when the window size did not change

GWT drops duplicate resize events (measured in Chrome 155: a synthetic resize with the same size produced no line)

Do not trigger layout through fake resize events; call the layout code directly.

The beforeunload dialog in Chrome shows no custom text ("message":"")

browsers ignore the text set by ClosingEvent.setMessage

Show the reason in the page when the user cancels the navigation, and save drafts automatically.

Cannot call method "setData" of null in GWTTestCase for a drag handler

HtmlUnit’s synthetic dragstart has no DataTransfer

Test the drag logic in a real browser or keep it out of the handler; real browsers supply dataTransfer.

A drop zone never receives drop in a real browser

per the HTML Standard the dragover event of the target must be cancelled (not reproduced in the probe)

Add a DragOverEvent handler that calls preventDefault(), as in the example.

ValueChangeEvent arrives twice for the same value

setValue(v, true) fires only on a difference, but a native change fires always (measured: value -> c twice)

Make the handler idempotent.

Where older material is outdated

  • Listeners and adapters. The book’s whole event chapter (listeners, ListenerCollection, adapters, onEventPreview) describes an API that is deprecated since GWT 1.6 and is still in the jar only for old code. The migration table above gives the replacements.

  • Event preview. The book overrides onEventPreview in a PopupPanel-style pattern; the replacement is Event.addNativePreviewHandler, and the old EventPreview is deprecated.

  • Drag and drop. The book has no native drag-and-drop and builds it from mouse listeners; GWT now wraps the HTML5 drag events and DataTransfer.

  • Browser quirks. The book’s event-model discussion centres on Internet Explorer differences (event objects, bubbling, mouse button codes). GWT 2.13.1 no longer carries an IE permutation, so GWT papers over only the differences that remain between the supported engines (user.agent values gecko1_8 and safari).

  • unload. Window.addCloseHandler and RootPanel.detachOnWindowClose rely on the unload event that browsers deprecated; both are deprecated in 2.12.0 and later, as the DOM & Elemental2 page explains.

  • mousewheel. Replaced by the standard wheel event inside GWT in 2.12.0.

What changed recently

  • 2.2.0. Touch events (touchstart, touchmove, touchend, touchcancel) were integrated into the GWT event framework.

  • 2.6 line. HandlerRegistrations.compose() and Widget.addBitlessDomHandler() were added, and @UiHandler learned to work with the bindery HandlerRegistration.

  • 2.12.0. Calls that need the unload event were deprecated (Window.addCloseHandler among them), and GWT’s internal use of mousewheel moved to wheel.

  • 2.13.x. No change to the event API; the handler interfaces, SimpleEventBus and ResettableEventBus are unchanged.

  • J2CL. The org.gwtproject.event library (org.gwtproject.event:gwt-event, module org.gwtproject.event.Event, classes org.gwtproject.event.shared.Event, EventBus and HandlerRegistration) is described by its README as a port of the bindery event module "with no dependency on gwt-user`" to prepare for GWT 3 and J2CL, with a compatibility bridge (`gwt-event-compat, CompatEventBus). Maven Central lists only 1.0.0-RC1 (2020-05-22) and the repository’s last push was in 2020-08-28, so it is a migration option to evaluate, not a drop-in for a 2.13 application (see J2CL & the Future).

References