Editors
|
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 (released 2026-06-19), gwt-maven-plugin 1.3.0 (net.ltgt.gwt.maven), JDK 21 running the compiler with -sourceLevel 17, the Jakarta Servlet 6 server of the Bookshelf scenario and the javax.validation 1.0 API that GWT still bundles, checked on 2026-10-09.
The Editor framework is client knowledge: the editors, the driver and the generated glue are Java compiled to JavaScript, the edited beans are usually shared classes, and the only build requirement is one <inherits> line.
The server never sees an editor; it receives the flushed bean through whatever transport the application uses.
Every output block was produced by compiling the examples of a private copy of the Bookshelf client with the 2.13.1 compiler and running them as GWTTestCase tests in HtmlUnit (the generator error messages come from compiling small probe modules with the same compiler); the numbers and messages are quoted as they were printed.
The Editor framework answers one question: how do I copy the data of an object graph into a form, let the user change it, and copy it back, without writing a setText(book.getTitle()) and a book.setTitle(box.getText()) for every field?
You describe the form as a class whose fields are named after the properties of the bean, you declare an empty driver interface, and the GWT compiler generates the code that walks both graphs.
Nothing is looked up by reflection at run time, so the framework works in the browser, where there is none.
The idea in one example
The Bookshelf book form is the simplest editor there is.
The class below is the real BookEditor of the scratch Bookshelf client.
public class BookEditor extends Composite implements Editor<Book> {
interface Binder extends UiBinder<Widget, BookEditor> {}
public interface Driver extends SimpleBeanEditorDriver<Book, BookEditor> {}
public interface Listener {
void onSave(Book edited);
}
private static final Binder BINDER = GWT.create(Binder.class);
@UiField TextBox title;
@UiField TextBox author;
@UiField IntegerBox year;
@UiField IntegerBox copies;
@UiField @Editor.Ignore Button save;
private final Driver driver = GWT.create(Driver.class);
private final Listener listener;
public BookEditor(Listener listener) {
this.listener = listener;
initWidget(BINDER.createAndBindUi(this));
driver.initialize(this);
}
public void edit(Book book) { driver.edit(book); }
@UiHandler("save")
void onSave(ClickEvent e) { listener.onSave(driver.flush()); }
}
Source: GWT — Editors: Quickstart
Four things make this work, and the rest of the page is about each of them:
-
The class implements
Editor<Book>, the marker that says "my package-visible fields edit the properties of a `Book`". -
The fields
title,author,yearandcopiesare named exactly like thegetTitle(),getAuthor(),getYear()andgetCopies()properties ofBook, and their types (TextBox,IntegerBox) know how to take part (they implementIsEditor). -
Driveris an interface with no methods;GWT.create(Driver.class)returns a class that GWT generated for this exact pair of types. -
edit(book)copies the bean into the widgets andflush()copies the widgets back into the same bean and returns it.
The module needs the framework once:
<module>
<inherits name="com.google.gwt.user.User" />
<inherits name="com.google.gwt.editor.Editor" />
<!-- ... -->
</module>
The classes live in gwt-user, so there is no new Maven dependency.
Editor.gwt.xml itself inherits com.google.gwt.core.Core, com.google.gwt.event.Event and com.google.gwt.validation.Validation, adds the source paths client and ui/client, and registers the generator described next.
The last inherit is the reason the framework has a (deprecated) dependency on bean validation; see Validation and error display.
Vocabulary
| Term | Meaning |
|---|---|
Bean |
Any object with strongly typed |
Editor |
An object that edits zero or more properties of a bean. It may be composed of sub-editors, and most editors are widgets, but the framework does not require it: a "headless" editor such as |
Driver |
The top-level controller that attaches a bean to the root editor and descends into the hierarchy. |
Delegate |
The peer object ( |
Adapter |
A provided class with a canned behaviour, for example |
Path |
The dotted name of a property from the root bean, such as |
The edit and flush cycle
The steps, as the Developer Guide lists them:
-
Create and initialise the editors (with UiBinder,
createAndBindUiis the moment). -
Create the driver with
GWT.createand callinitialize(editor). A driver is stateful, so one driver instance belongs to one editor hierarchy. -
Call
edit(bean)to push the data into the editors. -
Let the user work.
-
Call
flush()to copy the editor state into the bean, then askhasErrors()andgetErrors()whether an editor reported a problem.
| Method | Behaviour |
|---|---|
|
Binds the driver to the root editor. Must come first. |
|
Pushes the bean into the editors, clears the dirty state and remembers the bean. Returns nothing. |
|
Copies the editors into the bean passed to |
|
Whether the last |
|
True if any editor differs from the value it was given by the last |
|
Maps |
|
Walks the hierarchy with a visitor. The framework uses it for its own initialisation, flushing and error collection; |
The measured behaviour of the cycle, with the Bookshelf detail editor of the next sections (a book with a title, a subtitle, a year, an author and two reviews):
dirty after edit: false
dirty after typing: true
flush returns the edited instance: true, title=Effective Java, 3rd ed., hasErrors=false
dirty after flush: true
edit before initialize: java.lang.IllegalStateException: Must call initialize() first
flush before edit: java.lang.IllegalStateException: Must call edit() first
Three consequences follow:
-
flush()mutates the bean you gave toedit. If the user may cancel, give the driver a copy and discard it on cancel. -
isDirty()is the simple "unsaved changes" test, becauseflush()does not reset it: it stays correct while you validate, fail and let the user fix the form. -
To use the editors for a read-only view, call
editand never callflush.
How the generator finds the sub-editors
The driver interface is implemented by a generator, one of the deferred-binding generators described on the Deferred Binding & Generators page.
The module registers it for every type assignable to SimpleBeanEditorDriver:
<generate-with class="com.google.gwt.editor.rebind.SimpleBeanEditorDriverGenerator">
<when-type-assignable class="com.google.gwt.editor.client.SimpleBeanEditorDriver" />
</generate-with>
At compile time the generator reads the structure of your editor class and of the bean, builds a model of the whole tree, and writes a class named after the driver (for a top-level BookDetailDriver it is BookDetailDriverImpl).
Because the model is built before the permutations are generated, a mistake in the structure is a compile error, not a run-time surprise.
SimpleBeanEditorDriver of Book and BookEditor"] --> G["SimpleBeanEditorDriverGenerator
reads editor and bean types"] G --> M["Editor model:
paths, types, accessors"] M --> I["Generated DriverImpl
and delegates"] I --> J["Compiled JavaScript
no reflection"] G -. "structure error" .-> X["Compile error:
Failed to resolve the driver
via deferred binding"]
An editor exposes its sub-editors in one of three ways:
-
A field with at least package visibility whose name is the property name, or the property name followed by
Editor. -
A no-argument method with at least package visibility and the same naming rule. This allows an editor that is an interface.
-
The
@Pathannotation on the field or the method, with a dotted property path or a name that bypasses the naming rule.
@Ignore removes a field or method from consideration, and a sub-editor that is null is skipped.
Both annotations are nested in Editor, so they are written @Editor.Path and @Editor.Ignore, or simply @Path and @Ignore inside a class that implements Editor.
Here is the complete detail editor of the Bookshelf, using all of them.
public class BookDetailEditor extends Composite
implements Editor<BookDetail>, HasEditorErrors<BookDetail> {
// property name: edits getTitle()/setTitle()
TextBox title = new TextBox();
// property name + "Editor": edits getSubtitle()/setSubtitle()
TextBox subtitleEditor = new TextBox();
IntegerBox year = new IntegerBox();
// a sub-editor for a nested bean
AuthorEditor author = new AuthorEditor();
// @Path reaches into the same nested bean with a dotted path
@Path("author.name") Label authorLine = new Label();
// nullable property: the sub-editor is attached only while the value is non-null
OptionalFieldEditor<Publisher, PublisherEditor> publisher = OptionalFieldEditor.of(new PublisherEditor());
// IsEditor widget wrapping a ListEditor
ReviewListEditor reviews = new ReviewListEditor();
// looks like a property editor but is deliberately not part of the edited bean
@Ignore TextBox searchBox = new TextBox();
final List<String> shown = new ArrayList<>();
// (constructor adds the widgets to a FlowPanel)
@Override
public void showErrors(List<EditorError> errors) {
shown.clear();
for (EditorError error : errors) {
shown.add(error.getAbsolutePath() + " -> " + error.getMessage());
}
}
}
public interface BookDetailDriver extends SimpleBeanEditorDriver<BookDetail, BookDetailEditor> {}
Source: GWT — Editors: Editor contract
The generator’s model of that class, printed with EditorHierarchyPrinter.toString(driver) (trimmed to the paths):
com.example.bookshelf.client.editors.BookDetail
com.example.bookshelf.client.editors.BookDetailEditor
title java.lang.String ValueBoxEditor
subtitle java.lang.String ValueBoxEditor
year java.lang.Integer ValueBoxEditor
author Author AuthorEditor
author.name java.lang.String ValueBoxEditor
author.country java.lang.String ValueBoxEditor
author.name java.lang.String HasTextEditor
publisher Publisher OptionalFieldEditor
reviews java.util.List ListEditor
reviews[0] Review ReviewEditor
reviews[0].reviewer java.lang.String ValueBoxEditor
reviews[0].rating java.lang.Integer RatingEditor
reviews[0].text java.lang.String ValueBoxEditor
reviews[1] Review ReviewEditor
...
The table is a re-formatted extract: the real output prints each path, edited type and editor class on separate indented lines.
It also prints a last line per node that always reads ValueAwareEditor, even for leaf editors; the source builds that string with a misplaced conditional, so ignore that line.
Note that driver.toString() calls this printer only when GWT.isProdMode() is false; under HtmlUnit and in Super Dev Mode the code runs as JavaScript, the call returns the plain BookDetailDriverImpl@5d, and you have to call EditorHierarchyPrinter.toString(driver) yourself.
| Field | Property path | Reason |
|---|---|---|
|
|
field name equals the property |
|
|
the |
|
|
a sub-editor is an editor of the nested bean; the generator descends into it |
|
|
|
|
|
|
|
|
|
|
none |
|
The Developer Guide also shows the same idea for several editors on the same sub-bean (@Editor.Path("state") on two different editors of a BagOfState), which is how a bean with many properties is split into smaller editors; every editor edits only the subset it declares.
A measured detail about duplicates: when two editors map to the same path (author.name above is edited by a TextBox and by authorLine), both are asked for their value on flush() and the one visited later wins.
With the output of the run, authorLine.setText("changed in the Label") followed by flush() left book.getAuthor().getName() equal to changed in the Label.
Give every property one writer.
The Editor contract and its sub-types
All the interfaces are in com.google.gwt.editor.client; the adapters are in .adapters, and the widget-specific ones in com.google.gwt.editor.ui.client.
LeafValueEditor also extends TakesValue, which is why every TakesValue widget is almost a leaf editor already.
| Type | Use it when | What the framework calls |
|---|---|---|
|
The class only exposes sub-editors and has no behaviour that depends on the value. |
Nothing on the class itself: the generator reads its fields and accessors. |
|
A view wants to take part without implementing the editor contract, typically because it reuses the editor behaviour of something inside it. |
|
|
A non-object, immutable or opaque value that the framework must not descend into: a string in a text box, a boolean in a check box. |
|
|
The editor needs its |
|
|
The behaviour depends on the value being edited, or the editor wants an explicit "the cycle is over" notice. |
|
|
An unknown number of homogeneous sub-editors is attached at run time (a list, an optional bean). |
|
|
The editor wants to see, display or consume the errors of the editors below it. |
|
Source: GWT — Editors: Editor subtypes
The Developer Guide says the method that hands over the delegate is setEditorDelegate().
In GWT 2.13.1 it is HasEditorDelegate.setDelegate(EditorDelegate<T>); the guide text predates a rename, and a class that implements setEditorDelegate does not compile against the interface.
LeafValueEditor and recording an error
A leaf editor is a value in, a value out. The Bookshelf rating is a leaf that also keeps its delegate, so that it can say what is wrong with the value.
public class RatingEditor extends Composite
implements LeafValueEditor<Integer>, HasEditorDelegate<Integer> {
private final IntegerBox box = new IntegerBox();
private EditorDelegate<Integer> delegate;
public RatingEditor() {
initWidget(box);
}
@Override
public void setDelegate(EditorDelegate<Integer> delegate) {
this.delegate = delegate;
}
@Override
public void setValue(Integer value) {
box.setValue(value);
}
@Override
public Integer getValue() {
Integer value = box.getValue();
if (value == null || value < 1 || value > 5) {
// Reported to the nearest HasEditorErrors ancestor, or to the driver
delegate.recordError("Rating must be between 1 and 5", value, null);
}
return value;
}
}
Source: Javadoc — LeafValueEditor
EditorDelegate offers four services:
| Method | Purpose |
|---|---|
|
The path of the editor in the attached hierarchy, for example |
|
Report an input problem. The error goes to the nearest super-editor that implements |
|
Set the dirty flag. For a leaf editor the flag is computed from an |
|
Ask for notifications that the edited object changed elsewhere. It returns a |
Source: Javadoc — EditorDelegate
ValueAwareEditor
A ValueAwareEditor is told about the whole object, so it can change its own shape.
The Bookshelf publisher editor keeps a trace of the calls it receives.
public class PublisherEditor extends Composite implements ValueAwareEditor<Publisher> {
TextBox name = new TextBox();
TextBox city = new TextBox();
final List<String> trace = new ArrayList<>();
private EditorDelegate<Publisher> delegate;
@Override
public void setDelegate(EditorDelegate<Publisher> delegate) {
this.delegate = delegate;
trace.add("setDelegate");
}
@Override
public void setValue(Publisher value) {
trace.add("setValue " + (value == null ? "null" : value.getName()) + " at path '" + delegate.getPath() + "'");
city.setEnabled(value != null); // behaviour that depends on the value being edited
}
@Override
public void flush() {
trace.add("flush");
}
@Override
public void onPropertyChange(String... paths) {
trace.add("onPropertyChange");
}
}
Source: Javadoc — ValueAwareEditor
The trace for edit(book) with a publisher, then a flush(), then edit(book) after book.setPublisher(null):
trace: [setDelegate, setValue Addison-Wesley at path 'publisher']
trace after flush: [flush] publisher=Pearson
trace after edit(publisher=null): [], flush -> null
The order is the one the Developer Guide documents: the delegate first, then the value, and flush() last.
flush() is called depth first by the driver, so an editor does not flush its sub-editors; an editor that mutates its peer object directly should do so only in flush(), which is what lets a workflow be cancelled.
The empty trace after the null shows the OptionalFieldEditor at work: the publisher sub-editor was detached and not told anything.
onPropertyChange is never called by SimpleBeanEditorDriver, which has no subscription support.
HasEditorErrors
HasEditorErrors<T> has one method, showErrors(List<EditorError>).
The driver calls it, possibly with an empty list to clear a previous state.
Errors that the method does not consume continue to the next ancestor and finally to driver.getErrors().
| Method | Value |
|---|---|
|
Path from the root bean, |
|
Path relative to the editor that receives the error; an empty string if the error concerns that editor itself. |
|
The editor that holds the invalid value. |
|
The message, the offending value and the object given to |
|
Whether the error stops here. |
Source: Javadoc — EditorError
An invalid year typed into the IntegerBox and a rating of 9, then flush():
hasErrors=true year after failed parse=2018
driver error: abs=year path=year msg=Bad value (abc) value=abc editor=ValueBoxEditor userData=java.text.ParseException: abc
driver error: abs=reviews[1].rating path=reviews[1].rating msg=Rating must be between 1 and 5 value=9 editor=RatingEditor userData=null
showErrors received: [year -> Bad value (abc), reviews[1].rating -> Rating must be between 1 and 5]
Three details are visible there.
The text box itself reports unparsable input: ValueBoxEditor calls getValueOrThrow() and, on a ParseException, records Bad value ( followed by the text and ), with the exception as user data; the bean keeps its old year.
showErrors of an ancestor sees the errors of its descendants.
And because the method did not consume anything, the driver reports both.
When showErrors consumes the review errors (error.setConsumed(true) for paths starting with reviews), the same run gives:
showErrors received [year -> Bad value (abc), reviews[0].rating -> Rating must be between 1 and 5]; driver reports 1:
year
IsEditor versus Editor
IsEditor<E> is for a view that wants to reuse editor behaviour that lives in something it contains.
It is legal for a class to implement both; the editor returned by asEditor() is then a co-editor of the view.
The Developer Guide’s rule of thumb is: if the widget has several sub-editors in a simple, static hierarchy, implement Editor; if it reuses the editor behaviour of a type it wraps, implement IsEditor.
public class FramedLabel extends Composite implements IsEditor<LeafValueEditor<String>> {
private final Label wrapped = new Label();
public FramedLabel() {
FlowPanel frame = new FlowPanel();
frame.add(new InlineLabel("["));
frame.add(wrapped);
frame.add(new InlineLabel("]"));
initWidget(frame);
}
@Override
public LeafValueEditor<String> asEditor() {
return wrapped.asEditor();
}
}
An editor can also be an interface, with the sub-editors exposed as no-argument methods; the implementation holds the widgets.
This compiled and ran in the test; the values were title=Effective Java, subtitle(FramedLabel)=Third Edition and authorName=Joshua Bloch:
interface TitleView extends Editor<BookDetail> {
TextBox title();
FramedLabel subtitleEditor();
@Path("author.name") Label authorName();
}
interface TitleDriver extends SimpleBeanEditorDriver<BookDetail, TitleView> {}
Source: GWT — Editors: Editor contract
CompositeEditor
CompositeEditor<T, C, E extends Editor<? super C>> edits a variable number of homogeneous sub-editors.
You rarely implement it; ListEditor and OptionalFieldEditor are the two provided ones.
The three methods you would implement are:
-
createEditorForTraversal()returns a canonical sub-editor that the generator uses to compute all possible paths, so that an empty collection still has a structure to examine. -
getPathElement(E)returns the path fragment of a sub-editor, such as[2]for the third element of a list. -
setEditorChain(EditorChain<C, E>)gives the editor an object withattach(C object, E subEditor),detach(E subEditor)andgetValue(E subEditor). A sub-editor attached to the chain is flushed as if it were a static sub-editor.
Source: Javadoc — CompositeEditor
Provided adapters and widgets
| Adapter | Package | Purpose |
|---|---|---|
|
|
Keeps a |
|
|
A |
|
|
A composite and leaf editor for a bean property that can be |
|
|
A headless leaf editor that only holds a value ( |
|
|
Adapts a |
|
|
Adapts |
|
|
Adapts a |
|
|
A UiBinder-friendly decorator that shows the error of the wrapped box. It implements |
Source: GWT — Editors: Provided Adapters
Most of the widgets you already use implement IsEditor, which is why they can be used as fields without any adapter:
| Widget | asEditor() returns |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The plain ListBox is not in the table: it is not an editor, so use ValueListBox with a Renderer for a drop-down bound to a property.
A widget that is an IsEditor and a field of your editor is always treated as a property editor, even when you meant it as a plain label (see the pitfalls).
Three adapters in action, from one Editor<BookDetail> (the label is @Ignore`d because a `Label is an IsEditor too):
static class MiniEditor implements Editor<BookDetail> {
@Ignore final Label shown = new Label();
SimpleEditor<String> title = SimpleEditor.of(); // headless: just holds the value
HasTextEditor subtitleEditor = HasTextEditor.of(shown); // adapts HasText to LeafValueEditor<String>
ValueListBox<Integer> year = new ValueListBox<>(new AbstractRenderer<Integer>() {
@Override
public String render(Integer object) { return object == null ? "" : String.valueOf(object); }
});
}
Source: Javadoc — editor.client.adapters
SimpleEditor value=Effective Java, HasTextEditor label=Third Edition, ValueListBox=2018
after flush: title=Effective Java (3e) subtitle=Changed year=2001
ListEditor
A ListEditor keeps a List<T> property in sync with a list of sub-editors.
You supply an EditorSource, which creates, positions and disposes the sub-editors (usually widgets).
Wrapping the editor in an IsEditor widget avoids writing a delegate for each of its methods.
public class ReviewListEditor extends Composite
implements IsEditor<ListEditor<Review, ReviewEditor>> {
private final FlowPanel panel = new FlowPanel();
private final ListEditor<Review, ReviewEditor> editor = ListEditor.of(new ReviewEditorSource());
public ReviewListEditor() {
initWidget(panel);
}
@Override
public ListEditor<Review, ReviewEditor> asEditor() {
return editor;
}
/** Creates one ReviewEditor per list element and keeps the panel in step with the list. */
private class ReviewEditorSource extends EditorSource<ReviewEditor> {
@Override
public ReviewEditor create(int index) {
ReviewEditor sub = new ReviewEditor();
panel.insert(sub, index);
return sub;
}
@Override
public void dispose(ReviewEditor sub) {
sub.removeFromParent();
}
@Override
public void setIndex(ReviewEditor sub, int index) {
panel.insert(sub, index);
}
}
}
Source: Javadoc — ListEditor
ListEditor.getList() returns a wrapper that you mutate to add or remove rows, and getEditors() returns an unmodifiable list of the sub-editors.
The measured run on a book with two reviews, Ana and Luis:
editors=2 widgets in panel=2
after add+remove: editors=2 list=2 first=Ana same list object=true
after flush: Luis (edited), Marta
error reviews[2].rating -> Rating must be between 1 and 5
What the numbers show, confirmed by ListEditorWrapper in the source:
-
getList()is a working copy.addandremoveimmediately create or dispose sub-editors (editors=2after adding Marta and removing Ana), but the bean’s own list still has Ana first; the bean’s list is cleared and refilled only byflush(), in the same list instance. -
A sub-editor that is added later than the others gets its path from its position at the moment it was attached. After the
remove(0)the Marta editor is second but its error was reported asreviews[2], and in a second run the surviving Luis editor reportedreviews[1]after Ana was removed. Do not use a path of a list element as a lasting key after a structural change; match errors bygetEditor()orgetUserData(). -
EditorSource.create(int count, int index),createEditorForTraversal()(which creates and disposes one editor),disposeandsetIndexhave default implementations, so the minimum iscreate(int); overridedisposeto detach the widget andsetIndexto move it.
HasDataEditor.of(hasData) plays the same role for a cell widget: the list property becomes the rows of a CellTable or DataGrid, each row a leaf value (see the Cell Widgets page).
OptionalFieldEditor
Some properties are null until the user decides to fill them in.
OptionalFieldEditor.of(subEditor) attaches the sub-editor while the value is non-null and detaches it when it is null; it is also a LeafValueEditor, so the parent reads and writes the property through it.
| Action | Result (measured) |
|---|---|
|
|
|
|
|
|
Source: Javadoc — OptionalFieldEditor
The same holds for a nested bean without the adapter, with a trap: when author is null and the form has an AuthorEditor, the framework skips the sub-editor; the author widgets stay empty, whatever the user types is lost at flush(), and getAuthor() is still null with hasErrors() false.
Create the nested bean before edit, or use OptionalFieldEditor.
Validation and error display
The framework does not validate anything itself; it only displays what other code finds.
There are three sources of errors, and they all arrive as EditorError objects:
-
an editor calls
delegate.recordError(...)(theValueBoxEditorfor a parse error, the rating above for a range); -
a
javax.validation.ValidatorproducesConstraintViolationobjects and you give them todriver.setConstraintViolations(...); -
a RequestFactory server returns violations (see RequestFactory integration).
Constraint violations
setConstraintViolations(Iterable<ConstraintViolation<?>>) converts each violation to an error whose getUserData() is the original violation, delivers it to the editor at the violation’s property path, and returns whether unconsumed errors remain.
The bean is validated with the client-side Bean Validation of GWT (the Validation page covers the setup).
The Bookshelf beans carry the constraints:
public class BookDetail {
@NotNull
@Size(min = 3, message = "Title needs at least 3 characters")
private String title;
@Min(value = 1450, message = "Year must be 1450 or later")
@Max(2100)
private int year;
@Valid
private Author author; // Author.name: @NotNull @Size(min = 1, message = "Author name is required")
// ...
}
Source: GWT — Validation
and the save button of the form is this workflow, which compiled and ran:
public boolean save() {
BookDetail book = driver.flush();
if (driver.hasErrors()) {
return false; // a widget could not parse its input
}
Set<ConstraintViolation<BookDetail>> violations = validator.validate(book);
if (!violations.isEmpty()) {
// the driver takes Iterable<ConstraintViolation<?>>, so copy into a wildcard set
driver.setConstraintViolations(new HashSet<ConstraintViolation<?>>(violations));
return false;
}
sender.accept(book);
return true;
}
The four cases of the run:
1) title 'Hi': save=false sent=0 errors=1 first=title -> Title needs at least 3 characters
2) year 'x': save=false sent=0 errors=year -> Bad value (x)
3) valid: save=true sent=1 errors=0
4) server violation: errors=author.name -> Unknown author | HasEditorErrors saw [author.name -> Unknown author]
The server-side case uses a small adapter, because the framework reads only the root bean, the path text and the message:
public class ServerViolation implements ConstraintViolation<BookDetail> {
private final BookDetail root;
private final String path;
private final String message;
public ServerViolation(BookDetail root, String path, String message) {
this.root = root;
this.path = path;
this.message = message;
}
@Override public BookDetail getRootBean() { return root; }
@Override public String getMessage() { return message; }
@Override public Path getPropertyPath() {
return new Path() {
@Override public Iterator<Node> iterator() { throw new UnsupportedOperationException(); }
@Override public String toString() { return path; }
};
}
@Override public String getMessageTemplate() { return message; }
@Override public Class<BookDetail> getRootBeanClass() { return BookDetail.class; }
@Override public Object getLeafBean() { return root; }
@Override public Object getInvalidValue() { return null; }
@Override public ConstraintDescriptor<?> getConstraintDescriptor() { return null; }
}
The detailed run with three violations (title too short, year 1200, author name empty) printed:
violations=3
title | Title needs at least 3 characters | invalid=ab
author.name | Author name is required | invalid=
year | Year must be 1450 or later | invalid=1200
setConstraintViolations returned true, driver.hasErrors=true
driver error: title -> Title needs at least 3 characters editor=ValueBoxEditor userData=true
driver error: year -> Year must be 1450 or later editor=ValueBoxEditor userData=true
driver error: author.name -> Author name is required editor=ValueBoxEditor userData=true
HasEditorErrors on BookDetailEditor received: [title -> ..., year -> ..., author.name -> ...]
Rules the measurements and the source (SimpleViolation, BaseEditorDriver) establish:
-
The violation is matched to editors by the identity of its root bean, then by its property path string. Violations computed on a copy of the bean are ignored:
setConstraintViolationsreturnedfalseand left no errors for violations of another instance. Validate the object returned byflush(). -
The argument type is
Iterable<ConstraintViolation<?>>. ASet<ConstraintViolation<BookDetail>>does not convert; copy it into aSet<ConstraintViolation<?>>as above. -
An editor must exist for the path. A violation on a property that no editor edits has nowhere to go.
-
To show errors computed somewhere else, such as a server that validated the bean with Jakarta Validation and returned property paths and messages, implement
ConstraintViolationwith a root bean, a property path whosetoString()is the path and a message. The framework reads onlygetRootBean(),getPropertyPath().toString()andgetMessage(); theServerViolationclass of the example returnedUnknown authorforauthor.namethat way (case 4 above). Keep the server authoritative: the client check is a convenience, and the server must validate again.
Showing errors next to the field
ValueBoxEditorDecorator wraps a text box with an error label and is written to be used in UiBinder through the valuebox child element:
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui"
xmlns:e="urn:import:com.google.gwt.editor.ui.client">
<g:HTMLPanel>
<e:ValueBoxEditorDecorator ui:field="title"><e:valuebox><g:TextBox /></e:valuebox></e:ValueBoxEditorDecorator>
<e:ValueBoxEditorDecorator ui:field="author"><e:valuebox><g:TextBox /></e:valuebox></e:ValueBoxEditorDecorator>
<e:ValueBoxEditorDecorator ui:field="year"><e:valuebox><g:IntegerBox /></e:valuebox></e:ValueBoxEditorDecorator>
<e:ValueBoxEditorDecorator ui:field="copies"><e:valuebox><g:IntegerBox /></e:valuebox></e:ValueBoxEditorDecorator>
</g:HTMLPanel>
</ui:UiBinder>
public class BookFormEditor extends Composite implements Editor<Book> {
interface Binder extends UiBinder<Widget, BookFormEditor> {}
public interface Driver extends SimpleBeanEditorDriver<Book, BookFormEditor> {}
private static final Binder BINDER = GWT.create(Binder.class);
@UiField ValueBoxEditorDecorator<String> title;
@UiField ValueBoxEditorDecorator<String> author;
@UiField ValueBoxEditorDecorator<Integer> year;
@UiField ValueBoxEditorDecorator<Integer> copies;
public BookFormEditor() {
initWidget(BINDER.createAndBindUi(this));
}
}
The decorator is an IsEditor (its editor is the one of the contained box) and a HasEditorErrors that displays the messages of its own editor.
Rendered, with the year box set to abc and flush() called:
<!-- before -->
<div class="PPTOIHC-a-b"></div> <div class="PPTOIHC-a-a"><input type="text" value="2,018"></div>
<!-- after flush with "abc" -->
<div class="PPTOIHC-a-b" style="display: inline-block;">Bad value (abc)</div> <div class="PPTOIHC-a-a"><input type="text" value="abc"></div>
The title decorator beside it stayed display: none with an empty label.
The class names are obfuscated and differ between builds; style them from the decorator’s ui.xml or with your own wrapper if you need a look.
The decorator writes the message with setInnerText, not as HTML, so a message that contains markup is shown as text.
The same output shows a formatting trap: an IntegerBox renders 2018 as 2,018, because its renderer is the locale’s decimal format.
It parses 2,018 back to 2018, so nothing is lost, but a year or an identifier does not look right.
Use a TextBox with a validated text for such fields, or your own ValueBox subclass whose Renderer formats without grouping.
Validation in GWT is deprecated and needs one flag on current JDKs
Two facts about the validation module that the Editor module inherits:
-
The 2.8.0 release notes state: "Deprecate JSR 303 Bean Validation support (javax.validation, c.g.g.validation). The code hasn’t been maintained for years." It is
javax.validation1.0, notjakarta.validation, and it needs Hibernate Validator 4.1.0.Final classes and sources on the compile classpath (see the Validation page). The Editor framework itself does not need it:recordErrorand your ownConstraintViolationimplementation work without a Bean Validation provider. -
The 2.13.0 release notes: "jaxb and xml-apis are now optional when using GWT’s javax.validation support, set the gwt.validation.ignoreXml system property to avoid needing these". On JDK 21 there is no JAXB in the JDK, and without the property the validator generator fails (see the pitfalls table).
The test that used the validator ran with -DargLine=-Dgwt.validation.ignoreXml=true on the gwt:test goal (a surefire argument); for gwt:compile the plugin’s jvmArgs parameter passes JVM options to the forked compiler.
The Maven coordinates added to the client module for that test:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.1.0.Final</version>
<classifier>sources</classifier>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<classifier>sources</classifier>
</dependency>
The last dependency takes its version from the GWT BOM (validation-api 1.0.0.GA); since gwt-user already brings validation-api and its sources transitively, that entry is redundant in a project that depends on gwt-user.
The run printed log4j:WARN No appenders could be found for logger (org.hibernate.validator.util.Version), which is harmless.
Source: GWT — Release notes
RequestFactory integration
RequestFactoryEditorDriver<P, E> is the driver for editing RequestFactory proxies; it extends EditorDriver<RequestContext>, so its flush() returns the RequestContext that accumulated the edits.
The difference from the simple driver is how the bean becomes editable: RequestFactory proxies are immutable until a context calls edit(proxy), and the driver does that for every proxy it meets.
| Method | Purpose |
|---|---|
|
No subscription support: |
|
Subscription support: the driver listens for |
|
The property paths of the editor hierarchy, to pass to |
|
Start editing. The context gets the edits; the driver does not fire it. |
|
Read-only: show the data without a |
|
Copy the editors into the proxies and return the |
|
Show violations; |
The module for the generator is com.google.web.bindery.requestfactory.RequestFactory; it registers RequestFactoryEditorDriverGenerator for the driver the same way the Editor module does for the simple one.
This sketch compiled with the 2.13.1 compiler (client side only; no server was started):
static class BookRfEditor extends Composite implements Editor<BookProxy> {
TextBox title = new TextBox();
IntegerBox year = new IntegerBox();
}
interface Driver extends RequestFactoryEditorDriver<BookProxy, BookRfEditor> {}
private final Driver driver = GWT.create(Driver.class);
private final BookRfEditor editor = new BookRfEditor();
void start(Factory rf) {
driver.initialize(rf, editor); // with a RequestFactory: subscriptions work
}
void load(Factory rf, long id) {
BookRequest ctx = rf.bookRequest();
ctx.find(id).with(driver.getPaths()).fire(new Receiver<BookProxy>() {
@Override
public void onSuccess(BookProxy proxy) {
driver.edit(proxy, ctx); // the context is the one that will persist
}
});
}
void save() {
RequestContext ctx = driver.flush(); // the same context
if (driver.hasErrors()) {
return; // editor-level problems
}
ctx.fire(new Receiver<Void>() {
@Override
public void onSuccess(Void ignored) { /* done */ }
@Override
public void onConstraintViolation(Set<ConstraintViolation<?>> violations) {
driver.setConstraintViolations(violations); // server-side violations next to the fields
}
});
}
Source: GWT — RequestFactory
Receiver.onConstraintViolation(Set<ConstraintViolation<?>>) is the current hook; Receiver.onViolation(Set<Violation>) and RequestFactoryEditorDriver.setViolations are deprecated.
The Developer Guide’s FAQ says a read-only view is simply an editor whose driver is never flushed, and display(proxy) is the convenience for that.
The RequestFactory & AutoBeans page covers proxies, contexts and the server side; GWT 2.11 added the Jakarta artifacts for it.
The Bookshelf book form, end to end
The pieces above make up the Bookshelf’s edit screen:
driver.edit"] E --> U["User types in the
BookFormEditor boxes"] U --> S["Save: driver.flush"] S --> H{"hasErrors?"} H -- "yes" --> D["Decorators show
Bad value messages"] H -- "no" --> V["validator.validate and
setConstraintViolations"] V --> W{"violations?"} W -- "yes" --> D W -- "no" --> C["RPC saveBook
XSRF-protected"] C --> N["PlaceController goes to the catalog"]
The activity of the scratch Bookshelf (the real class, reduced to the editor lines) loads the book over GWT-RPC, hands it to the editor, and on save sends the flushed bean:
public class EditActivity extends AbstractActivity implements BookEditor.Listener {
@Override
public void start(AcceptsOneWidget panel, EventBus eventBus) {
BookEditor editor = new BookEditor(this);
panel.setWidget(editor);
factory.getCatalog().getBook(id, new AsyncCallback<Book>() {
@Override
public void onSuccess(Book book) { editor.edit(book); }
@Override
public void onFailure(Throwable caught) { GWT.log("getBook failed", caught); }
});
}
@Override
public void onSave(Book edited) {
factory.getCatalog().saveBook(edited, new AsyncCallback<Book>() { /* ... */ });
}
}
Source: GWT — Editors
The server must not trust the bean it receives because an editor produced it.
The editor is a convenience of the browser, so the server re-validates every field, re-checks that the signed-in user may edit that book, and copies only the fields that the form is allowed to change from the received bean into the stored entity; sending a whole entity back and saving it as it arrives lets a hand-made request overwrite copies or an owner id (the over-posting problem).
The XSRF protection of the RPC service (see GWT-RPC and XSRF, RPC & Server Security) applies to the save call as usual.
Testing editors
-
The generated driver needs
GWT.create, so a test that uses the real driver is aGWTTestCase(see Testing). The examples on this page ran that way, in HtmlUnit, in a few seconds each. -
The package
com.google.gwt.editor.client.testingprovides test doubles for code that depends on the framework without compiling it:MockSimpleBeanEditorDriver,MockEditorDelegate,MockEditorError,MockEditorChain,FakeEditorContext,FakeEditorSource,FakeLeafValueEditorand theEditorHierarchyPrinterused above. The RequestFactory side hasMockRequestFactoryEditorDriver. -
Keep the logic of a presenter independent of the driver and test it with plain JUnit; the Bookshelf’s
CatalogPresenterTestfollows that approach.
Pitfalls
| Symptom | Cause | Fix |
|---|---|---|
|
A package-visible field of a type that is an editor ( |
Rename the field to a property, add |
|
A misspelt |
Fix the path; every segment needs a getter |
|
A |
End the path at the primitive |
|
The editor class contains an editor of its own type for a self-referencing bean ( |
Break the cycle: write a second editor type for the deeper level, or edit the self-reference with a |
|
The driver interface extends something besides |
The driver must extend exactly one interface |
|
|
Declare |
|
The editor implements the raw |
|
|
|
Follow the order: |
A field is never filled in and nothing is reported |
The field is private, or it is |
Package-visible or accessor; assign the sub-editor before |
Text typed into a sub-editor of a |
The nested bean is |
Create the bean before |
Two errors for one bad box after |
|
Avoid calling |
An error for a list element has an old index ( |
The path is fixed when the sub-editor is attached; removing a row does not renumber the others |
Identify the row by |
|
The violations belong to another instance than the one passed to |
Validate the object |
|
Generic invariance |
|
|
The validator generator reads validation XML with JAXB, which is not in JDK 11 and later |
Set |
An |
Its renderer is the locale decimal format |
Use a |
Two widgets write the same property |
Both map to the same path; the later one in the hierarchy wins at |
One editor per property |
The driver is created with |
The class does not exist; it is generated |
|
Where older material is outdated
| Statement | State in 2.13.1 |
|---|---|
Forms are wired by hand (the approach of GWT in Action) |
The book describes GWT 1.3 and 1.4 and predates the framework by several years (the Developer Guide marks Editors as new in 2.1): its forms read and write each widget by hand, with |
|
|
|
A typo in the guide; the annotation takes the path and no semicolon. |
|
The client-side validation module is deprecated since 2.8.0 and stays on |
What changed recently
-
2.8.0 (2016): the Bean Validation support that
setConstraintViolationsrelies on was deprecated. -
2.11.0 (2024): Jakarta artifacts for GWT-RPC and RequestFactory, which matter for the save call of an editor-based screen.
-
2.13.0 (2026): JAXB and
xml-apisare optional for the validator whengwt.validation.ignoreXmlis set. -
org.gwtproject.editor:gwt-editor(GWT Editor) is a port of the framework without the dependency ongwt-user, aimed at J2CL and GWT 3. The repositorygwtproject/gwt-editorwas last pushed on 2024-12-17, Maven Central has only1.0.0-RC1(2020-10-05), and it replaces the generator with an annotation processor (gwt-editor-processor) and the moduleorg.gwtproject.editor.Editor. It requires GWT 2.9.0 or newer. Use it when the application is moving to J2CL; keepcom.google.gwt.editor.Editorfor a GWT 2 application.