Migrate a Java desktop app to the web with AI: a decision guide

Asking an AI assistant to convert a whole Swing app in one pass sounds fast. In practice you get thousands of lines nobody has reviewed, written against APIs the model half remembers, and a release date that keeps slipping.
There is a better way to migrate a Java desktop app to the web with AI: move it one screen at a time. Some screens are worth rebuilding in webforJ with an assistant connected to the webforJ MCP server. Others keep running as they are, embedded in the web app through WebswingConnector, until their turn comes. Your business logic stays in Java, and users get a web app from the first release.
The hard part is deciding which screen goes which way. This guide gives you a simple test for each screen, the brief to hand the assistant, and the limits to plan around.
Why not let AI convert the whole app?
A prompt like "convert this Swing app to a web app" produces code that looks plausible and fails in ways you find late. A desktop app holds years of event wiring, modal dialogs, and state that lives in widgets. A general-purpose model has no map of the web framework you are targeting, so it guesses at class names, and you spend the saved time fixing guesses.
Two things change that. First, the target has to be a framework where the UI is written in Java, so the assistant maps Swing concepts to Java concepts instead of crossing into HTML, CSS, and a second language. webforJ builds every screen from Java components, which keeps a migrated screen close to the original structure. Second, the assistant needs live access to the real API surface. That is the job of the webforJ MCP server, which lets the assistant search the webforJ knowledge base, scaffold projects, and validate class names instead of recalling them from training data.
Three ways to migrate
Most desktop apps need a mix. Pick per screen, not per app.
| Path | What happens | Best for |
|---|---|---|
| Embed | Webswing streams the unchanged Swing app into a webforJ page | Complex editors, charting canvases, screens nobody wants to change |
| Rebuild with AI | An assistant writes webforJ views from the Swing screen | Forms, lists, dashboards, CRUD screens |
| Keep the logic, replace the shell | Service and domain classes are reused, only the UI layer is new | Apps with a clean split between model and view |
The embed path is a real release strategy. It is how you ship the first web release while the rebuild is still running.
Embed first: ship the old screen
Webswing runs a Swing, JavaFX, or SWT app on the server and streams its interface to the browser, with no change to the original source. webforJ wraps that in the WebswingConnector component, available since 25.10. The Webswing integration docs describe the setup: a Webswing server hosts the app, and your webforJ view points at its URL.
@Route("orders")
public class LegacyOrdersView extends Composite<Div> {
private final WebswingConnector legacy =
new WebswingConnector("http://localhost:8080/orders-desktop/");
private final Button refresh = new Button("Refresh orders");
public LegacyOrdersView() {
legacy.setSize("100%", "600px");
refresh.onClick(e -> legacy.performAction("refresh"));
legacy.onAction(event -> {
if ("orderSelected".equals(event.getActionName())) {
event.getActionData().ifPresent(id -> refresh.setText("Order " + id));
}
});
getBoundComponent().add(refresh, legacy);
}
}
The connector is bidirectional. performAction sends a command to the Swing app, and onAction receives the actions the Swing app sends back. The action names and data formats are defined by your Swing code through the Webswing Java API, which is covered in Communication. That channel is how a new webforJ toolbar can drive a legacy screen, and how a legacy selection can update a new web panel next to it.
One practical rule from the setup docs: Webswing and webforJ need different ports, and cross-origin embedding needs the allowed origins configured in the Webswing admin console. Do this on day one, because it is the first thing that blocks a demo.