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.
| Source | Examples | Dispatched by | Registered with |
|---|---|---|---|
DOM events of one element |
|
the browser, then |
|
Logical (widget) events |
|
the widget itself, when its state changes |
|
Global browser events |
native preview of every event, |
|
|
Application events |
a custom |
your own code, through an event bus or a |
|
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.
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:
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.
| 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 |
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 |
Removing twice |
Harmless: the probe called |
Combining |
|
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();
}
Source: GWT Javadoc — Widget
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.
| Native event | Handler | Event class | Widget method |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
the matching |
|
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:
| API | What it gives |
|---|---|
|
the event name; the element that was hit; the element whose listener is running; the other element in |
|
coordinates in the viewport and on the screen. |
|
the mouse button; compare with the |
|
the key code of |
|
modifier keys. The GWT wrappers add |
|
the |
|
the wheel deltas; |
|
the |
|
cancel the browser’s default action, stop the event from bubbling (also on |
Source: GWT Javadoc — DomEvent
Two properties of DomEvent are easy to get wrong.
-
Events are flyweights. Each
DomEvent.Typeholds one event instance that is reused for every dispatch of that type.DomEvent.fireNativeEventstores the current native event in it, fires it, and puts the previous value back. A handler therefore must not keep theClickEventfor later; copy what is needed (getClientX(), the target element). -
Stock events are accessed on the same call stack only.
GwtEvent.getSource()and theDomEventaccessors 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
}
});
Source: GWT Javadoc — ValueBoxBase
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.
| Event and handler | Typical source and meaning |
|---|---|
|
|
|
tab and tree selection. The before-event can be cancelled with |
|
open and close of tree items, disclosure panels and popups. |
|
the browser window ( |
|
every widget, when it is attached or detached ( |
|
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
Source: GWT Javadoc — ValueChangeEvent
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.
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:
| Method | Behaviour |
|---|---|
|
registers the handler and sinks the native event, using |
|
registers the handler and always sinks the event by name; for events that have no bit (added in the 2.6 line) |
|
registers the handler on the widget’s handler manager and does nothing else. Use it for events that the widget (or you) fire with |
Source: GWT Javadoc — Widget
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:
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.
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();
Source: GWT Javadoc — Event
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.
| Method | Handler and event | Notes |
|---|---|---|
|
|
the client size; GWT filters duplicate events when the size did not change |
|
|
fired before the page unloads ( |
|
|
scrolling of the page |
|
|
deprecated (needs the |
Source: GWT Javadoc — Window
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.
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));
Source: GWT Javadoc — Window
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.
| Call | What it stops | Typical use |
|---|---|---|
|
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 ( |
|
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 |
|
delivery of the event to GWT widgets (global, before any widget) |
modal overlays and global shortcuts |
Source: MDN — Event.preventDefault()
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());
Source: MDN — Event.stopPropagation()
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)withElement.DRAGGABLE_TRUE,DRAGGABLE_FALSEorDRAGGABLE_AUTOsets thedraggableattribute. -
The seven events
dragstart,drag,dragend(on the dragged element) anddragenter,dragover,dragleave,drop(on the target) have the handlers and events listed in the DOM-events table, all subclasses ofDragDropEventBase.DragDropEventBase.isSupported()tells whether the browser supports them.FocusWidget(buttons, text boxes) has theadd...Handlermethods; other widgets useaddDomHandler(handler, DragStartEvent.getType()). -
DragDropEventBase.setData(format, data)andgetData(format)are shortcuts for theDataTransferobject returned bygetDataTransfer().DataTransferofferssetData,getData,clearData,setDragImage(Element, x, y)andsetDropEffect(DropEffect);DropEffectis an enum of the browser’s effects.
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());
Source: GWT Javadoc — DragStartEvent
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).
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); }
}
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); }
}
Source: GWT Javadoc — Event
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.
| Class | Package | Role |
|---|---|---|
|
|
the current API for new code: |
|
|
the event base class, the key, the registration and the exception that collects handler failures |
|
|
the GWT flavour used by widgets. |
|
|
compatibility wrappers over the bindery classes for code written before the move. The abstract |
Source: GWT Javadoc — EventBus
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
Source: GWT Javadoc — SimpleEventBus
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.
| 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 |
A handler is added while firing |
the add is queued and applied when the outermost |
A handler is removed while firing |
queued the same way |
Null arguments |
|
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
Source: GWT Javadoc — SimpleEventBus
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));
Source: GWT Javadoc — ResettableEventBus
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
}
Source: GWT Javadoc — ValueChangeEvent
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.
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:
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"));
}
}
Source: GWT Javadoc — Widget
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
onLoadand remove them inonUnload(or use aResettableEventBusandremoveHandlers()); -
give each presenter or activity its own scoped bus;
-
keep the
HandlerRegistrationof 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:
| Listener (deprecated) | Replacement |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
not needed: |
Source: GWT Javadoc — Widget
What changes besides names:
-
The sender. A listener method received the sender widget (
onClick(Widget sender)); a handler receives an event. Useevent.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 withregistration.removeHandler(). Storing the registration is the new duty. -
Logical versus DOM. The old
ChangeListenerfired for user changes and forsetText; the newChangeHandlerfires only for the browser’schangeevent. Code that depended on the old behaviour needs aValueChangeHandler(andsetValue(v, true)when it sets values from code). -
Event preview. The old
EventPreviewreturned a boolean fromonEventPreview; the new handler callscancel()orconsume()on the event. -
@UiHandler. In UiBinder templates,@UiHandler("save")on a method with aClickEventparameter 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 aValueChangeEventand the data of aDropEventcome from the user or from another origin. Show them withsetText/setInnerTextorSafeHtml(see SafeHtml & XSS), neversetHTMLorsetInnerHTMLwith concatenated data.BookRowusessetInnerTextfor 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 |
|
Call |
A handler added with |
|
Use |
Handlers added with |
the override did not call |
Always call |
|
a |
Copy the needed values inside the handler. Do not keep events. |
|
a handler threw; the other handlers ran, and the failures are collected in one exception ( |
Fix the failing handler; catch |
|
the |
Pass non-null arguments. A source-specific registration needs a real source object. |
|
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 |
|
Do not rely on a new handler seeing the event that is being dispatched. |
A removed widget keeps reacting to bus events ( |
a handler registered on an application bus in the constructor was never removed; the bus holds the lambda |
Register in |
Two native preview handlers run in an unexpected order, or an event "disappears" |
preview handlers run in reverse registration order, and |
Order the registrations deliberately, check |
|
GWT drops duplicate resize events (measured in Chrome 155: a synthetic |
Do not trigger layout through fake resize events; call the layout code directly. |
The |
browsers ignore the text set by |
Show the reason in the page when the user cancels the navigation, and save drafts automatically. |
|
HtmlUnit’s synthetic |
Test the drag logic in a real browser or keep it out of the handler; real browsers supply |
A drop zone never receives |
per the HTML Standard the |
Add a |
|
|
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
onEventPreviewin aPopupPanel-style pattern; the replacement isEvent.addNativePreviewHandler, and the oldEventPreviewis 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.agentvaluesgecko1_8andsafari). -
unload.Window.addCloseHandlerandRootPanel.detachOnWindowCloserely on theunloadevent that browsers deprecated; both are deprecated in 2.12.0 and later, as the DOM & Elemental2 page explains. -
mousewheel. Replaced by the standardwheelevent 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()andWidget.addBitlessDomHandler()were added, and@UiHandlerlearned to work with the binderyHandlerRegistration. -
2.12.0. Calls that need the
unloadevent were deprecated (Window.addCloseHandleramong them), and GWT’s internal use ofmousewheelmoved towheel. -
2.13.x. No change to the event API; the handler interfaces,
SimpleEventBusandResettableEventBusare unchanged. -
J2CL. The
org.gwtproject.eventlibrary (org.gwtproject.event:gwt-event, moduleorg.gwtproject.event.Event, classesorg.gwtproject.event.shared.Event,EventBusandHandlerRegistration) is described by its README as a port of the bindery event module "with no dependency ongwt-user`" to prepare for GWT 3 and J2CL, with a compatibility bridge (`gwt-event-compat,CompatEventBus). Maven Central lists only1.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).
Source: GWT — Release Notes