跳至主要内容
← Blog

GWT alternative: keep your UI in Java with webforJ

· 一分钟阅读
webforJ Team
Do it all in Java! Build in Java, deploy to the Web.

cover image

webforJ is a GWT alternative for teams that want to keep writing their UI in Java but no longer want a Java-to-JavaScript compile step in the middle. You build screens from Java components, they run on the server inside your Spring Boot app, and the browser renders them. There is no client-side code to compile and no RPC layer between the UI and your services.

This post compares the two models, maps GWT concepts to their webforJ counterparts, and shows how a team can move an existing GWT app one screen at a time.

What GWT does and where it stands​

The GWT project describes itself as a development toolkit for browser-based applications. Its compiler turns your Java source into JavaScript: "GWT compiles your Java source code into optimized, stand-alone JavaScript files." You write client-side applications in Java and deploy them as JavaScript.

The project is still releasing. The GWT versions page lists 2.13.0 from February 2026 and 2.13.1 from June 2026. If your GWT app works today, nothing forces a move. The reason teams look at a GWT alternative is usually the shape of the architecture, and that's where webforJ differs.

Client-side Java versus server-side Java​

In GWT, UI code runs in the browser after translation to JavaScript. Server code is ordinary Java and, in the words of the GWT RPC guide, it "doesn't need to be translatable, so you're free to use any Java library you find useful". The client is a separate program with its own rules about which Java it can use.

In webforJ, UI code is ordinary Java running on the server, in the same process as your services. The browser renders the components and sends events back. The whole app, UI included, uses the full JDK and any library on the classpath.

QuestionGWTwebforJ
Where UI code runsIn the browser, as compiled JavaScriptOn the server, as Java
Build step for the UIJava-to-JavaScript compilationNormal Java compilation
UI to service callsRPC with async interfaces and callbacksDirect method calls
Types crossing the boundaryMust meet GWT serialization rulesNone, they never cross
Libraries in UI codeThose the client side can translateAnything on the classpath
Layout templatesUiBinder XML or panels in JavaJava composition

What happens to RPC?​

A GWT RPC call needs several pieces. According to the GWT documentation, you define a service interface that extends RemoteService, a matching Async interface with an AsyncCallback parameter on each method, and a servlet that extends RemoteServiceServlet. The client creates a proxy with GWT.create() and handles the result in a callback. Parameters and return values must follow GWT's serialization rules.

In webforJ that layer has no reason to exist, because the view and the service live in the same JVM. A view receives the service through its constructor and calls it like any other Java object. There is no Async twin of the interface, no callback and no data transfer class created only to cross the wire. The Spring Boot integration guide shows constructor injection into views.

Server-side rendering does mean the UI depends on a live connection to the server. For internal business applications, that's usually an acceptable trade for the simpler architecture.

How GWT concepts map to webforJ​

Most of what you know carries over. The names change and the number of moving parts shrinks.

GWTwebforJ
Button, TextBoxButton and TextField
FlowPanel, VerticalPanel, HorizontalPanelFlexLayout with a direction and spacing
DockLayoutPanelAppLayout with header, drawer and footer
CellTableTable bound to a repository or a list
DialogBoxDialog with header, content and footer sections
ClickHandler and other handlersonClick(...) and the other on methods
UiBinder template and owner classA Java class that composes components
RPC serviceAn injected Spring bean

For the GWT side, see the panels guide, the cell table guide, and the DialogBox reference. The panels guide itself notes that VerticalPanel can usually be replaced by a simple FlowPanel, which is the same instinct as reaching for a FlexLayout in column direction.

What happens to UiBinder templates?​

UiBinder pairs a ui.xml template with an owner Java class. Elements marked with ui:field become @UiField members, and @UiHandler methods take the place of listeners.

In webforJ there is no second file. The layout is Java, so the template, the fields and the handlers collapse into one class. A field you declared as @UiField becomes a plain variable, and a @UiHandler becomes a lambda passed to onClick. Because layout is code, loops and conditionals are available where a template had none. A view built from components is a Composite, and the composing components guide covers the pattern.

What does a typical screen look like in webforJ?​

Here is a customer list with a search field and an add dialog. The service is a Spring bean injected through the constructor.

@Route(value = "customers", outlet = Shell.class)
public class CustomersView extends Composite<FlexLayout> {
private final CustomerService service;
private final Table<Customer> table = new Table<>();

public CustomersView(CustomerService service) {
this.service = service;

TextField search = new TextField("Search");
search.onValueChange(e -> table.setItems(service.find(e.getValue())));

table.addColumn("Name", Customer::name);
table.addColumn("City", Customer::city);
table.setItems(service.find(""));

Button add = new Button("Add customer", ButtonTheme.PRIMARY);
add.onClick(e -> openEditor());

getBoundComponent().setDirection(FlexDirection.COLUMN).setSpacing("1em");
getBoundComponent().add(search, add, table);
}

private void openEditor() {
Dialog dialog = new Dialog();
TextField name = new TextField("Name");
TextField city = new TextField("City");
Button save = new Button("Save", ButtonTheme.PRIMARY);
save.onClick(e -> {
service.save(new Customer(name.getValue(), city.getValue()));
table.setItems(service.find(""));
dialog.close();
});
dialog.addToContent(name, city);
dialog.addToFooter(save);
dialog.open();
}
}

service.find(...) is a direct call that returns a list. There is no callback and no serialization step. @Route gives the screen its own URL, and outlet places it inside a shared layout, which is the role a GWT app usually gives to a history token and a root panel.

The shell is another small class with an AppLayout and an AppNav for the menu:

public class Shell extends Composite<AppLayout> {
public Shell() {
AppNav nav = new AppNav();
nav.addItem(new AppNavItem("Customers", CustomersView.class, TablerIcon.create("users")));
getBoundComponent().addToDrawer(nav);
}
}

How do you migrate from GWT to webforJ?​

Treat the move as a screen-by-screen rewrite of the UI layer, while the service layer stays as it is.

  1. Create the webforJ app beside the GWT one. Generate a Spring Boot project with startforJ. Point it at the same database and reuse the same service classes, or move them into a shared module.
  2. Pick one screen. A read-only list or a small form is a good first target. Rebuild it with a Table or a FlexLayout, and call the service directly.
  3. Delete the RPC plumbing for that screen. The Async interface, the servlet registration and the transfer classes used only by that screen can go.
  4. Fold the template into the class. Convert the ui.xml structure into component construction, and the @UiHandler methods into listeners.
  5. Move users over. Run both apps during the transition and link between them by URL until the last screen is done.

Services that already sit behind RPC are the easy part. Their implementation classes are plain Java, and in webforJ you inject them instead of exposing them through a servlet.

Let an AI assistant port the screens

Install the webforJ AI plugin in Claude Code, GitHub Copilot, Cursor, Gemini CLI or Codex. The MCP server gives the assistant the current webforJ documentation and API, so it writes against the real framework instead of guessing, and the Agent Skills guide it through tasks such as building forms and creating components. Hand it a ui.xml file with its view class, ask for the webforJ screen that calls the same services, and review the result before you merge it.

Is webforJ right for your team?​

Choose webforJ if your team knows Java, wants to stay in Java for the UI, and runs business applications where a Spring Boot backend is the center of gravity. You get one language and one build, and the code that used to cross a network boundary becomes a method call. If you also want an installable app on the desktop or home screen, the installable apps post shows how it works.

To try it, generate a project with startforJ or follow the getting started guide. Then rebuild one list screen from your GWT app and compare the two.

Frequently asked questions​

Is GWT still maintained?​

The GWT project site lists release 2.13.0 from February 2026 and 2.13.1 from June 2026. An existing GWT app doesn't have to move for that reason alone.

Does webforJ compile Java to JavaScript?​

No. webforJ components are Java classes that run on the server. The browser renders them with web components and sends events back to the server.

What replaces GWT RPC in webforJ?​

Direct method calls. A view gets a Spring service through its constructor and calls it in the same JVM, so there is no async interface, no callback, and no serialization contract to maintain.

Do I have to rewrite everything at once?​

No. Services stay as plain Java, and you can rebuild the UI one screen at a time while both apps run. Each migrated screen lets you delete its RPC code.

Building something with webforJ?

Talk to the team about your project, a migration, or whether webforJ fits.

Contact us