Environment
Jmix version: 3.0.x (data model diagram, jmix-datatools)
Bug Description
The data model diagram availability check (DiagramEngine.pingService()) pings the root endpoint / of the PlantUML server, while the diagram itself is rendered from /plantuml/png/<encoded>.
PlantUmlDiagramEngine:
protected String createURLTemplate() {
return "/plantuml/png/%s"; // rendering: baseUrl + /plantuml/png/<encoded>
}
@Override
public boolean pingService() {
...
responseStatus = restClient.head() // no uri → HEAD against the root of baseUrl
.retrieve().toBodilessEntity().getStatusCode();
} catch (ResourceAccessException exception) {
return false;
}
return responseStatus.is2xxSuccessful() || responseStatus.is3xxRedirection();
}
This works only for the default public server, because https://www.plantuml.com/ happens to serve a landing page at the root. For a self-hosted plantuml/plantuml-server container the two endpoints are mutually exclusive:
| plantuml-server deployment |
/ (ping) |
/plantuml/png/... (rendering) |
default (ROOT, no BASE_URL) |
200 → ping OK |
404 → rendering broken |
BASE_URL=plantuml |
404 → ping broken |
200 |
| public plantuml.com |
200 (landing) |
200 |
Because the /plantuml prefix is hardcoded in the URL template, a self-hosted server must be started with BASE_URL=plantuml for rendering to work at all — and that is exactly the configuration in which the root ping returns 404. Serving a blank page at the root just to satisfy the ping is a workaround for an inconsistency inside the add-on, not a deployment mistake.
There is a second, independent defect in the same code path: a 404 does not make pingService() return false — it throws. RestClient.retrieve() throws HttpClientErrorException on 4xx by default, and only ResourceAccessException is caught. The call site in DataModelListView.onDiagramButtonClick is outside the try block:
if (!diagramEngine.pingService()) { // ← outside try/catch, NotFound propagates
notifications.create(messageBundle.getMessage("diagramGeneration.error.serviceUnavailable.message"))
.withType(Notifications.Type.ERROR)
.show();
return;
}
try {
byte[] diagramData = generateDiagram(entityModelsDc.getItems());
dataModelDiagramViewSupport.open(this, diagramData);
} catch (Exception e) {
log.error("Diagram generation failed", e);
notifications.create(messageBundle.getMessage("diagramGeneration.error.generationFailed.message"))
.withType(Notifications.Type.ERROR)
.show();
}
So instead of the intended "service unavailable" notification the user gets a generic unhandled-exception dialog.
Steps To Reproduce
- Run a PlantUML server the way the add-on's URL template requires:
services:
plantuml:
image: plantuml/plantuml-server:jetty
environment:
BASE_URL: plantuml
ports:
- "8080:8080"
- Point the application at it:
jmix.datatools.data-model-diagram.host=http://localhost:8080
- Open the Data Model view and click Diagram.
Current Behavior
HEAD http://localhost:8080/ returns 404 (the app is served at /plantuml), pingService() throws HttpClientErrorException$NotFound, and the exception escapes the click listener as an unhandled error. Rendering via /plantuml/png/... would have worked.
Conversely, with a server deployed at ROOT (no BASE_URL) the ping succeeds and the generation request 404s, so no self-hosted configuration works end to end unless the root path additionally serves something that answers 2xx/3xx.
Expected Behavior
The availability check should target an endpoint the add-on actually uses, and a self-hosted PlantUML server should work without adding a dummy page at the root.
Suggested fixes:
- Minimal — ping the same prefix that is used for rendering (
HEAD /plantuml/) instead of /. Fixes the BASE_URL=plantuml case; a ROOT deployment stays unsupported.
- Preferred — make the prefix configurable, e.g.
jmix.datatools.data-model-diagram.path (default plantuml), and use it both in the URL template and in the ping. Both deployment layouts then work, and the knowledge about paths stays inside DiagramEngine, so the pingService() contract is not affected for future EngineTypes.
- In any case — treat any HTTP response as "service is reachable" and only network errors/timeouts as "unavailable":
onStatus(status -> true, (req, res) -> {}) or widen the catch to RestClientException. Also move pingService() inside the existing try block in DataModelListView so it can never produce an unhandled exception.
- Alternative — drop the separate ping entirely. It is an extra round trip before every generation, and the existing
try/catch already covers failures; distinguishing the exception type is enough to choose between the "service unavailable" and "generation failed" messages.
Worth checking while fixing:
- Behaviour when
host already contains a path (e.g. http://plantuml:8080/plantuml). DefaultUriBuilderFactory has known subtleties about whether a base path is kept or replaced by a URI starting with /; the resulting URL should be pinned by a test and documented, otherwise this intuitive setting silently produces /plantuml/plantuml/png/... or works by accident.
- The client sets a default
Accept: image/png header for all requests, including the ping; some servers/proxies may answer HEAD with 406. Another argument for treating any HTTP response as "alive".
Documentation
https://docs.jmix.io/jmix/data-tools/data-model.html#data-model-diagram documents only jmix.datatools.data-model-diagram.host for self-hosting. It does not mention that the server must expose /plantuml/png/... (i.e. that BASE_URL=plantuml is required for the official image). A working docker-compose snippet in the docs would help.
Sample Project
No sample project — the issue is reproducible with any Jmix 3 application plus the docker-compose snippet above.
Environment
Jmix version: 3.0.x (data model diagram,
jmix-datatools)Bug Description
The data model diagram availability check (
DiagramEngine.pingService()) pings the root endpoint/of the PlantUML server, while the diagram itself is rendered from/plantuml/png/<encoded>.PlantUmlDiagramEngine:This works only for the default public server, because
https://www.plantuml.com/happens to serve a landing page at the root. For a self-hostedplantuml/plantuml-servercontainer the two endpoints are mutually exclusive:/(ping)/plantuml/png/...(rendering)BASE_URL)BASE_URL=plantumlBecause the
/plantumlprefix is hardcoded in the URL template, a self-hosted server must be started withBASE_URL=plantumlfor rendering to work at all — and that is exactly the configuration in which the root ping returns 404. Serving a blank page at the root just to satisfy the ping is a workaround for an inconsistency inside the add-on, not a deployment mistake.There is a second, independent defect in the same code path: a 404 does not make
pingService()returnfalse— it throws.RestClient.retrieve()throwsHttpClientErrorExceptionon 4xx by default, and onlyResourceAccessExceptionis caught. The call site inDataModelListView.onDiagramButtonClickis outside thetryblock:So instead of the intended "service unavailable" notification the user gets a generic unhandled-exception dialog.
Steps To Reproduce
jmix.datatools.data-model-diagram.host=http://localhost:8080Current Behavior
HEAD http://localhost:8080/returns 404 (the app is served at/plantuml),pingService()throwsHttpClientErrorException$NotFound, and the exception escapes the click listener as an unhandled error. Rendering via/plantuml/png/...would have worked.Conversely, with a server deployed at ROOT (no
BASE_URL) the ping succeeds and the generation request 404s, so no self-hosted configuration works end to end unless the root path additionally serves something that answers 2xx/3xx.Expected Behavior
The availability check should target an endpoint the add-on actually uses, and a self-hosted PlantUML server should work without adding a dummy page at the root.
Suggested fixes:
HEAD /plantuml/) instead of/. Fixes theBASE_URL=plantumlcase; a ROOT deployment stays unsupported.jmix.datatools.data-model-diagram.path(defaultplantuml), and use it both in the URL template and in the ping. Both deployment layouts then work, and the knowledge about paths stays insideDiagramEngine, so thepingService()contract is not affected for futureEngineTypes.onStatus(status -> true, (req, res) -> {})or widen the catch toRestClientException. Also movepingService()inside the existingtryblock inDataModelListViewso it can never produce an unhandled exception.try/catchalready covers failures; distinguishing the exception type is enough to choose between the "service unavailable" and "generation failed" messages.Worth checking while fixing:
hostalready contains a path (e.g.http://plantuml:8080/plantuml).DefaultUriBuilderFactoryhas known subtleties about whether a base path is kept or replaced by a URI starting with/; the resulting URL should be pinned by a test and documented, otherwise this intuitive setting silently produces/plantuml/plantuml/png/...or works by accident.Accept: image/pngheader for all requests, including the ping; some servers/proxies may answer HEAD with 406. Another argument for treating any HTTP response as "alive".Documentation
https://docs.jmix.io/jmix/data-tools/data-model.html#data-model-diagram documents only
jmix.datatools.data-model-diagram.hostfor self-hosting. It does not mention that the server must expose/plantuml/png/...(i.e. thatBASE_URL=plantumlis required for the official image). A working docker-compose snippet in the docs would help.Sample Project
No sample project — the issue is reproducible with any Jmix 3 application plus the docker-compose snippet above.