JHipster — What We Keep, What We Drop

1. Overview

admin-service was generated with JHipster. So was registration-portal. So was admin-ui (JHipster 7, long out of sync). admin-portal is greenfield — not generated — and so is any future portal.

This doesn’t mean we throw JHipster’s patterns away. Many are good. It means we cherry-pick: the backend service generators remain useful, the generator-driven frontend scaffolding does not. This page is the explicit inventory of which JHipster patterns we keep (and carry into new greenfield work) and which we drop.

If you’re bootstrapping a new EMS service or portal: read this, then copy from the reference implementations — registration-portal for portals, admin-service for backends.

2. Backend (Spring Boot) — Keep

2.1. Kept wholesale

Pattern Why

Maven POM structure with profile-driven build variants

Proven at scale; operators know what -Pprod,kubernetes,otlp means. See Maven POM Conventions.

config/ package organising @Configuration classes

Easy to discover; one concern per file

Liquibase with dev / prod contexts + faker data

Gold standard for schema-migrations-as-code

JWT via Nimbus with custom claims (orgId, personId, linkedOrgs)

Battle-tested claim shape; extensions layered on top (see OIDC Token Exchange)

web/rest/ package for controllers

One @RestController per resource; OpenAPI picks up everything automatically (see OpenAPI Contract Flow)

@RestControllerAdvice ExceptionTranslator

Consistent error response shape across endpoints

Logback config with CRLF converter + ANSI/plain switching per profile

Safe log output; K8s-friendly

Startup banner in main() via logApplicationStartup(env)

Operator diagnostic; see SpringApplication Bootstrap

Profile validation rejecting dev+prod combos

Cheap safety net; log a loud error on misconfiguration

jhipster.* properties namespace for framework-level toggles

Retained from the JHipster framework library; we don’t invent our own

Hazelcast second-level cache + Spring Cache @Cacheable

Required in admin-service; opt-in per service; see Hazelcast Configuration

springdoc-openapi auto-generation

Reflection-based spec; @Operation / @Tag on top

2.2. Kept as principle, customised heavily

Pattern Our adaptation

SecurityConfig filter chain

Stock JHipster has JWT and anonymous access. We layer API-key filter, tenant-resolution filter, OIDC login, and the JWT relay filter (portals) on top. Radically different configuration, same skeleton.

Caching configuration

Stock has user/authority caches. admin-service extends with userOrgAccess / userPersonAccess security-dimension caches. Same CacheConfiguration.java class, custom map configs.

DTO / Mapper pattern

Stock uses Mapstruct. We use it too. The handful of DTOs where Mapstruct’s defaults didn’t fit (e.g. OAuth2TokenExchangeRequestDTO) are hand-written — exception not rule.

Exception-translation

Stock returns RFC-7807 ProblemDetail. We follow it. Extend with custom fields for X-Token-Expired, tenant context, etc.

3. Backend — Drop

Pattern Why

Auto-generated entity CRUD endpoints

We don’t run jhipster entity <name> anymore. Entity changes are edit-in-place on the existing entity + Liquibase changelog + hand-written resource. The generator’s churn (overwriting manual changes on re-run) costs more than it saves.

JDL import

Related to the above. We model schema by writing Liquibase XML directly; JDL adds a translation layer we don’t benefit from.

Elasticsearch integration

Stock JHipster offers spring-data-elasticsearch. We don’t use it. Remove from POMs if it leaked in.

User / authorities as separate service

Microservice split. admin-service is a monolith; users and authorities live alongside business entities. Don’t split unless there’s a reason.

Gatling performance tests in-repo

We run load tests out-of-repo. Empty src/test/gatling/ was noise; removed.

.jhipster/*.json entity metadata

Generator state that tracks each entity. We don’t regenerate; the files go stale; delete.

.yo-rc.json

Generator configuration. Kept for historical reference but the answers it records no longer reflect reality (e.g. applicationType: gateway in admin-ui is meaningless now). Don’t rely on.

4. Frontend (Angular) — Keep

4.1. Kept wholesale

Pattern Why

core/ / entities/ / shared/ tree structure at the module level

Imperfect but workable. Feature modules can live under entities/ (rename to features/ if wanted; it’s cosmetic).

app/core/config/application-config.service.ts

Single place for microservice endpoint wiring. See OpenAPI Contract Flow for how it interacts with the generated client.

JHipster language / i18n stack

Full i18n infrastructure for free: @ngx-translate, JSON-per-language files, webpack merge at build time

Alert / toast model

Integrates with the stock ExceptionTranslator on the backend; portal shows server-emitted alerts

@ngx-webstorage session/local storage abstraction

Standardised; avoids direct window.localStorage calls

dayjs integration (date pipes, helpers, interceptor for JSON → dayjs)

Core of our client-side date handling — extracted into @ems/shared-ui (see Portal Pattern)

Interceptor chain shape (auth / error-handler / auth-expired / notification)

Composable, well-ordered; add new interceptors into the chain rather than overriding

Auth directive *jhiHasAnyAuthority

Clean route-protection primitive; extracted into @ems/shared-ui

Sort / filter directives

Retained functionally; restyled when Bootstrap 4 visual artefacts leak

Pagination helpers

Server-side pagination assumption that matches admin-service controllers

core/interceptor/ module structure

Same as backend reasoning — one concern per file

4.2. Kept as principle, customised heavily

Pattern Our adaptation

environment.ts / environment.prod.ts

registration-portal skips these in favour of webpack DefinePlugin with DEBUG_INFO_ENABLED. See Dev-Mode UI Awareness. Pick one, document which.

Feature module structure by entity

We prefer module-by-bounded-context (events, membership, persons, finance). CRUD-per-entity was a generator artefact.

JHipster routes (UserRouteAccessService guard on every route)

Stock JHipster adds this guard to every generated route. Many become no-ops. We apply it only to routes that actually need authority checks (see registration-portal’s cleanup US #497).

Styling (Bootstrap 4 / ng-bootstrap)

Retained ng-bootstrap; Bootstrap 4 theme replaced with PrimeNG + custom styles. The retained primitives adapt.

5. Frontend — Drop

Pattern Why

Generator-driven entity modules

Same reasoning as backend — the generator overwrites manual edits on re-run. We don’t regenerate.

entities/ tree populated by jhipster entity

Goes stale. For admin-portal: build module-by-bounded-context; admin-ui’s 52 generated entity modules are an anti-pattern.

Bootstrap 4 visual artefacts coupled into jhiSort / filter directives

Retain the directives functionally, restyle with our own CSS.

Translated error messages from i18n/error.json containing every possible HTTP error

Too many; unmaintained. Keep a minimal set and fall back to server-provided messages.

swagger-ui bundled into webpack output

Adds 2+ MB to the SPA bundle for a feature we don’t use in the portal. Skip unless the portal specifically wants an in-app API explorer.

Cypress in registration-portal’s test/javascript/

Kept optionally. If E2E tests are running, great; if not, don’t ship the infra unused.

JHipster login / register / password-reset components (registration-portal generated them)

Unused when OIDC is the only auth path. Delete.

@ngx-infinite-scroll, ng2-dragula, various small libraries JHipster adds by default

Audit and prune. Only keep what the app actually imports.

6. Generator-Driven Regeneration — Dropped Entirely

We do not regenerate. Period. If we ever do again, it is a project-wide decision and it is preceded by a design-journal thread evaluating alternatives.

Reasons:

  1. We diverge from generator assumptions. Custom caches, custom auth flows, custom tenant resolution — all would fight a regeneration attempt.

  2. The frontend generator’s output drifts from our Angular version. admin-ui was Angular 14; registration-portal is Angular 16; new generator output is Angular 17+. Each regeneration is a migration too.

  3. Manual work always follows regeneration. Non-trivial merge of the delta into our customisations. The hours saved by scaffolding are lost in the merge.

  4. Claude Design pushes us further away. When the target UI is designed, not templated, regenerator templates are a liability.

The JHipster generator has delivered two good things we keep: a proven POM + profile layout (backend) and a set of reusable primitives (frontend). We take those and move on.

7. Reference

File Role

admin-service/pom.xml

Backend reference POM (JHipster 8.11-derived)

registration-portal/pom.xml

Portal reference POM (JHipster 8.11-derived)

admin-ui/ (legacy)

Pre-pattern reference — what not to carry forward

.yo-rc.json (in each service)

Historical generator configuration; kept for reference, not load-bearing

9. Change History

Date Change

2026-04-24

Initial draft. Synthesises the WS1 docs and the admin-portal-greenfield design-journal thread.