Project Setup and Detailed Measurements
Part 4-7: Project Setup and Detailed Measurements
This chapter documents the full setup, methodology, and raw measurements behind the comparisons throughout Part 4. It is the reference layer — the summaries in Part 4-1 through 4-6 draw from the data here.
Test environment — Level 1 (Employee Manager)
Level 1 used Spring Boot 2.7.7 with the javax namespace for ZK, Thymeleaf, Wicket, and React. A shared Maven module provided the backend entities, repositories, and service layer — all frameworks used the same backend code.
Vaadin required its own separate Spring Boot 3.x setup with the Jakarta namespace, which meant it could not participate in the shared javax module. Vaadin’s Level 1 entities were duplicated from the shared module.
Test environment — Level 2 (HR Workspace)
Level 2 standardized all six implementations on Spring Boot 3.3.4 with the Jakarta namespace, under a single multi-module Maven project. The backend was no longer shared at Level 2 — each framework provided its own data layer, since Level 2 focused on frontend component richness rather than backend integration patterns.
React and Angular were standalone frontend applications at Level 2 — no Spring Boot backend. They used hardcoded mock data. This reflects realistic Level 2 frontend-focused development, and is noted in the measurements.
Test environment — Level 3 (Live HR Operations Center)
Level 3 used Spring Boot 3.3.4 with the Jakarta namespace (apps3/), reusing the Level 2 shared module as a Maven dependency. Only ZK and Vaadin provided full implementations. The other four frameworks provided explanation cards documenting the implementation approach and estimated effort.
All measurements were taken on the same hardware under consistent conditions, with JDK 17 and clean builds (tests skipped). Response times are warm averages (requests 2–5 after one initial warm-up request).
Level 1: Employee Manager — Full Measurements
Lines of code
The shared backend module (7 files, 398 lines) is counted once and excluded from per-framework totals.
| Framework | UI Lines | Backend Lines | Total | JS Written |
|---|---|---|---|---|
| ZK | 937 (Java VM + ZUL) | — (shared) | 937 | 0 |
| Vaadin | 1,064 (Java only) | — (shared*) | 1,064 | 0 |
| Thymeleaf | 1,038 (HTML + Java) | — (shared) | 1,038 | 0 |
| Wicket | 1,006 (Java + HTML) | — (shared) | 1,006 | 0 |
| React | 730 (JSX) + 307 (Java API) | 307 | 1,037 | 730 |
| Angular | 798 (TS) + 295 (Java API) | 295 | 1,093 | 798 |
*Vaadin required entity duplication due to Jakarta namespace incompatibility with the shared javax module.
Build times
| Framework | Build Time | Notes |
|---|---|---|
| Thymeleaf | ~2.2s | Maven only |
| Wicket | ~2.4s | Maven only |
| ZK | ~2.6s | Maven + WAR packaging |
| React | ~3.3s | Maven + Vite |
| Vaadin | ~6.6s | Maven + Vaadin plugin (downloads Node) |
| Angular | ~7.5s | Maven + ng build |
Deployable artifact size
| Framework | JAR Size | Frontend JS | Notes |
|---|---|---|---|
| React | 39 MB | 215 KB (72 KB gzip) | Smallest payload |
| Thymeleaf | 40 MB | None | Server-rendered |
| Wicket | 43 MB | None | Server-rendered |
| ZK | 60 MB | 1,520 KB | ZK framework cached after first load |
| Vaadin | 88 MB | 2,678 KB | Largest on both dimensions |
Server response times (warm, localhost, avg of requests 2–5)
| Framework | Endpoint | Avg Response |
|---|---|---|
| Vaadin | GET /employees | ~5–8 ms |
| Thymeleaf | GET /employees | ~7–9 ms |
| React | GET /api/employees | ~5–10 ms |
| Angular | GET /api/employees | ~6–14 ms |
| ZK | GET /index.zul | ~11–13 ms |
| Wicket | GET /employees | ~14–24 ms |
Architecture complexity
| Framework | Layers | State | Languages |
|---|---|---|---|
| Vaadin | 1 | Server session | Java only |
| ZK | 2 | Server session | Java + ZUL |
| Thymeleaf | 2 | Stateless | Java + HTML |
| Wicket | 2 | Server session | Java + HTML |
| React | 3 | Client (useState) | Java + JSX |
| Angular | 4 | RxJS + services | Java + TypeScript |
Level 2: HR Workspace — Full Measurements
Component implementation status
Each view is classified by how the component was delivered:
- ✅ Native — built-in component from the framework vendor, no external library
- 🔷 Ecosystem wrapper — official framework-specific package (e.g. @fullcalendar/react, PrimeReact, PrimeNG)
- 🔶 Directory add-on or CDN — Vaadin Directory add-on (free, Java API), or CDN-loaded JS library (correct pattern for server-side frameworks with no component library)
- 💰 Commercial placeholder — paid component available but not implemented; placeholder shown
- ⚠️ No solution — no framework-appropriate solution found at any tier
| View | ZK | Vaadin | Thymeleaf | Wicket | React | Angular |
|---|---|---|---|---|---|---|
| Event Calendar | ✅ <calendars> (native EE) |
🔶 FullCalendar for Flow (Directory, FlowingCode) | 🔶 FullCalendar CDN | 🔶 FullCalendar CDN via renderHead | 🔷 @fullcalendar/react | 🔷 @fullcalendar/angular |
| Org Chart | ✅ <organigram> (native EE) |
🔶 OrgChart Add-on (Directory, FlowingCode) | 🔶 OrgChart.js CDN | 🔶 OrgChart.js CDN via renderHead | 🔷 PrimeReact OrganizationChart (MIT) | 🔷 PrimeNG p-organizationChart (MIT) |
| Salary Pivot | ✅ <pivottable> (native EE) |
🔶 PivotTable for Vaadin (Directory, Vaadin Component Factory) | 🔶 PivotTable.js CDN | 🔶 PivotTable.js CDN via renderHead | 🔷 react-pivottable (MIT) | 💰 Syncfusion / DevExtreme (commercial — placeholder) |
| Portal Dashboard | ✅ <portallayout> + <charts> (native EE) |
✅ Vaadin Dashboard + Charts (Vaadin Pro, commercial) | 🔶 Gridstack.js + Chart.js CDN | 🔶 Gridstack.js + Chart.js CDN via renderHead | 🔷 react-grid-layout + recharts (MIT) | 🔷 angular-gridster2 + ng2-charts (MIT) |
| Views delivered | 4/4 | 4/4 | 4/4 | 4/4 | 4/4 | 3/4 |
| Native framework components | 4 | 1 (Dashboard + Charts, commercial) | 0 | 0 | 0 | 0 |
| Ecosystem wrappers | — | — | — | — | 4 | 3 |
| Directory add-ons / CDN | — | 3 (Directory add-ons, free Java API) | 4 (CDN) | 4 (CDN) | — | — |
| Commercial placeholder | — | — | — | — | — | 1 (Pivot) |
Key notes on Vaadin: The three Directory add-ons (FullCalendar for Flow, OrgChart Add-on, PivotTable for Vaadin) expose Java APIs — the developer writes no JavaScript. They come from third-party vendors on the Vaadin Directory (FlowingCode, Vaadin Component Factory), not from Vaadin Ltd directly. Each has its own release schedule separate from Vaadin core.
Key notes on Angular: No free native Angular pivot table component exists. Syncfusion and DevExtreme both offer commercial solutions. The Angular implementation shows a placeholder for this view.
Lines of code and JavaScript written
| Framework | Total LOC | JS Written | Views Delivered | Notes |
|---|---|---|---|---|
| ZK | 430 | 0 | 4/4 | All native ZK components; Java ViewModel + ZUL tags |
| Vaadin | 592 | 0 | 4/4 | Directory add-ons use Java API — no JS strings required |
| Thymeleaf | 274 | ~80 (inline <script> tags) |
4/4 | CDN lib init code in HTML templates |
| Wicket | 337 | ~100 (Java string-embedded) | 4/4 | JS embedded in OnDomReadyHeaderItem Java strings |
| React | ~265 | ~265 (JSX) | 4/4 | FullCalendar, PrimeReact OrgChart, react-pivottable, react-grid-layout + recharts |
| Angular | ~410 | ~410 (TypeScript) | 3/4 | FullCalendar, PrimeNG OrgChart, angular-gridster2 + ng2-charts; Pivot = placeholder |
Third-party libraries required
| Framework | Libraries Needed | Integration Pattern |
|---|---|---|
| ZK | 0 | All native framework components |
| Vaadin | 3 Directory add-ons + 1 native commercial | Java API (no JS); from 3 different vendors |
| Thymeleaf | 4 (FullCalendar, OrgChart.js, PivotTable.js, Gridstack.js + Chart.js) | CDN <script> tags |
| Wicket | 4 (same as Thymeleaf) | CDN via JavaScriptHeaderItem.forUrl() + renderHead |
| React | 5–6 NPM packages (from 4 vendors) | Framework-native wrappers (@fullcalendar/react, primereact, react-pivottable, react-grid-layout, recharts) |
| Angular | 4–5 NPM packages (from 3–4 vendors) | Framework-native wrappers; Pivot requires commercial license |
Build times and artifact sizes (Level 2)
| Framework | Build Time | JAR Size | Frontend JS | Notes |
|---|---|---|---|---|
| Thymeleaf | ~2.1s | 47 MB | CDN only | Minimal deps |
| Wicket | ~2.2s | 49 MB | CDN only | Wicket framework bundled |
| React | ~2.1s† | N/A† | 920 KB (275 KB gzip) | Standalone frontend; FullCalendar, react-pivottable, react-grid-layout |
| ZK | ~3.5s | 76 MB | ~1.5 MB (ZK + ZKCharts) | Larger than Level 1 due to zkcharts and zkmax JARs |
| Angular | ~6.3s† | N/A† | 756 KB (202 KB gzip) | Standalone frontend; Pivot = commercial placeholder |
| Vaadin | ~6.9s | 89 MB | ~2.8 MB | Includes Highcharts, Lit/Polymer; slowest build |
†Standalone frontend build — no Spring Boot backend included in this measurement.
Server response times — Level 2 (warm, localhost, avg of requests 2–5)
| Framework | Endpoint | Avg Response Time | Notes |
|---|---|---|---|
| React | GET / (Vite dev) | ~1–2 ms | Static HTML shell |
| Angular | GET / (ng serve) | ~1–2 ms | Static HTML shell |
| Thymeleaf | GET /calendar | ~2–3 ms | Server-rendered, CDN component init |
| Wicket | GET /calendar | ~2 ms | CDN component init |
| ZK | GET /calendar.zul | ~7–9 ms | Full calendar component render + session init |
| Vaadin | GET /calendar | ~7–8 ms | Vaadin AJAX-based navigation |
JavaScript written by developer — Level 2
The Level 2 JS-written metric is the sharpest differentiator between frameworks at this complexity level:
| Framework | JS/TS Written | Source |
|---|---|---|
| ZK | 0 lines | All 4 views use native ZK components |
| Vaadin | 0 lines | Directory add-ons expose Java API — no JS required |
| Thymeleaf | ~80 lines | Inline <script> blocks initializing CDN libraries |
| Wicket | ~100 lines | JS embedded in Java string literals via renderHead |
| React | ~265 lines | All UI logic in JSX across 4/4 real npm components |
| Angular | ~410 lines | TypeScript across 3/4 real components; Pivot = placeholder |
ZK and Vaadin both achieve zero JavaScript at Level 2. The distinction between them is who owns the components: ZK’s are all from Potix Corporation, Vaadin’s are from Vaadin Ltd (Dashboard) plus three third-party Directory vendors.
Level 3: Live HR Operations Center — Full Measurements
Scope
Two features were fully implemented in ZK and Vaadin. React, Angular, Thymeleaf, and Wicket ran as minimal Spring Boot applications but showed placeholder views — styled panels rendered within the app in place of each unimplemented feature, describing the libraries, infrastructure, and developer effort that would be required.
Feature 1: Large Dataset Grid (10,000 rows)
| Metric | ZK | Vaadin | React | Angular | Thymeleaf | Wicket |
|---|---|---|---|---|---|---|
| Built-in virtualization | ✅ ROD (1 XML attribute) | ✅ DataProvider.fromCallbacks() | ❌ External library required | ⚠️ CDK Virtual Scroll (built-in, needs wiring) | ❌ None | ⚠️ Pagination only |
| DOM nodes at runtime | ~20 (viewport only) | ~50–100 (page window) | Library-dependent | Library-dependent | All rows | Current page |
| Server round-trip on scroll | None (data in Java heap) | Yes (offset/limit query per page) | N/A | N/A | N/A | N/A |
| Developer UI code | 64 lines (32 VM + 32 ZUL) | 50 lines | ~200 LOC JS + library | ~150 LOC TS + CDK wiring | ~200 LOC JS | ISortableDataProvider impl |
| JavaScript required | 0 | 0 | ~200 | ~150 | ~200 | 0 (pagination only) |
ZK note: For datasets too large for Java heap, ZK also provides BigListBox + MatrixModel (ZK EE), where Java heap usage stays constant regardless of dataset size — only the visible rows occupy memory.
Vaadin note: DataProvider.fromCallbacks() makes a database query per scroll page. Developer writes two lambda callbacks; Vaadin handles the UI-side virtualization.
Angular note: Angular CDK Virtual Scroll is built-in, which is an advantage over React (external library). However, the developer must wire it explicitly: itemSize strategy, trackBy functions, viewport connection.
Wicket/React/Angular/Thymeleaf note: LOC estimated — these frameworks were not fully implemented at Level 3. The LOC represents a reasonable implementation scope based on the integration approach described, not measured code.
Feature 2: Real-Time Server Push Dashboard
| Metric | ZK | Vaadin | React | Angular | Thymeleaf | Wicket |
|---|---|---|---|---|---|---|
| Built-in mechanism | ✅ EventQueue (APPLICATION scope) | ✅ @Push + UI.access() | ❌ None | ❌ None | ❌ None | ⚠️ AbstractAjaxTimerBehavior (polling) |
| Transport | WebSocket/Comet (automatic) | WebSocket (via @Push) | STOMP/SockJS or native WS | RxJS WebSocket | SSE/WebSocket | Interval polling |
| Thread safety | Automatic | ui.access() required by developer | Developer-managed | Developer-managed | Developer-managed | N/A |
| Developer push code | ~15 lines | ~30 lines | ~350 lines | ~300 lines | ~400 lines | ~50 lines (polling) |
| JavaScript required | 0 | 0 | ~350 | ~300 | ~400 | 0 |
ZK note: APPLICATION-scoped EventQueue broadcasts to all connected sessions from a single publisher. Developer subscribes in ViewModel; thread safety is handled by ZK automatically.
Vaadin note: @Push must be on AppShellConfigurator (not AppLayout — placing it there causes a startup RuntimeException). All UI updates must be wrapped in ui.access() to ensure thread safety.
Wicket note: AbstractAjaxTimerBehavior is client-initiated polling, not true server push. Suitable for low-frequency updates; not suitable for applications requiring immediate push delivery. The LOC represents a reasonable implementation scope based on the integration approach described, not measured code.
React/Angular/Thymeleaf note: Achieving real-time push requires assembling WebSocket infrastructure on both server and client sides. None of these frameworks provide a built-in mechanism. The LOC represents a reasonable implementation scope based on the integration approach described, not measured code.
Total UI LOC and JavaScript written — Level 3
| Framework | Grid LOC | Push LOC | Total UI LOC | JS Required |
|---|---|---|---|---|
| ZK | 64 (VM + ZUL) | 72 (VM + ZUL) | 136 | 0 |
| Vaadin | 50 | 110 | 160 | 0 |
| React | N/A (explanation card) | N/A | — | Library + integration required |
| Angular | N/A (explanation card) | N/A | — | CDK + integration required |
| Thymeleaf | N/A (explanation card) | N/A | — | Library + JS infrastructure required |
| Wicket | N/A (explanation card) | N/A | — | 0 (polling only) |
Level 4: Enterprise Requirements — WCAG Accessibility
Accessibility support by framework
| Framework | Built-in Module | Coverage | Manual Effort | Overall Effort |
|---|---|---|---|---|
| Vaadin | ✅ Core (always on) | Very strong — WCAG Compatible | Moderate | Low |
| ZK | ✅ za11y.jar | Very Strong — WCAG Compatible | Moderate | Low–Medium |
| Angular | ✅ @angular/cdk/a11y | Moderate | Moderate | Medium |
| React | ⚠️ Third-party only | Library-dependent | Moderate–Extensive | Medium–Very High |
| Wicket | ❌ None | Weak | Extensive | High |
| Thymeleaf | ❌ None | None | Everything | Very High |
Framework-level accessibility detail
Vaadin — Accessibility is built into every component. No separate configuration required. Remaining manual work is limited to application-level semantics (page landmarks, form labels where context is custom).
ZK — Adding za11y.jar to the classpath retrofits WAI-ARIA roles, keyboard navigation, screen reader live regions, and high-contrast support across all ZK components. Some manual work required for application-specific patterns not covered by the module.
Angular — The @angular/cdk/a11y package provides LiveAnnouncer, FocusTrap, ListKeyManager, and high-contrast detection. These are useful building blocks, but component libraries (PrimeNG, Angular Material) have inconsistent accessibility quality. More manual testing and remediation than ZK or Vaadin.
React — No built-in accessibility support. The best path is choosing an accessible component library (Radix UI, Adobe React Spectrum, Headless UI), but coverage depends entirely on library choice and version. High variability.
Wicket — No accessibility module. All ARIA roles, labels, keyboard navigation, and focus management must be written explicitly by the developer. High effort for full WCAG compliance.
Thymeleaf — A template engine with no component model and no accessibility scaffolding. Every semantic landmark, ARIA attribute, keyboard handler, and focus behavior is the developer’s responsibility.
Security and enterprise support summary
| Framework | Commercial Support | Security Process | Long-term Assurance |
|---|---|---|---|
| ZK | ✅ Potix Corporation | Snyk + CodeQL + SonarQube CI/CD; ISO 27001; OSCP pen testing | Commercial — direct financial incentive |
| Vaadin | ✅ Vaadin Ltd | Internal security practices; formal audit program | Commercial — direct financial incentive |
| React | ❌ Community only | Meta internal practices; community CVE reporting | Meta-backed; ecosystem dependencies not guaranteed |
| Angular | ❌ Community only | Google internal practices; community CVE reporting | Google-backed; ecosystem dependencies not guaranteed |
| Thymeleaf | ❌ Community only | Community CVE reporting | Apache/community maintained |
| Wicket | ❌ Community only | Community CVE reporting | Apache maintained — slow but stable |
Notes on methodology
All response times were measured warm — after one initial request to allow JVM JIT compilation and connection pool initialization. Response times represent the server processing time at localhost; network latency is excluded.
Line counts include all developer-written code in the application layer. Generated code, framework internals, and the shared backend module are excluded from per-framework totals.
For Level 2, JavaScript embedded inside Java string literals (as in Wicket’s renderHead pattern) is counted as JavaScript written by the developer — it is application code, regardless of the surrounding language.
The Level 2 measurements reflect fully functional implementations wherever a framework-appropriate solution was available. Where a commercial placeholder was used (Angular’s pivot table), it is noted and excluded from line counts.
The React and Angular Level 2 applications were built as standalone frontend applications with no Spring Boot backend, using hardcoded mock data. This reflects how these frameworks are typically developed when the backend is separate — the frontend is a self-contained application.
Level 3 line counts for React, Angular, and Thymeleaf are estimates only — these frameworks were not implemented, and no code was written or measured. The figures in the Feature 1 and Feature 2 tables reflect the expected scope of a complete implementation based on the documented integration patterns, not actual measurements.