Troubleshooting#

Aggregated quick-reference for the errors that show up once and then never again — if you know what they mean. Grouped by where they surface.

Backend — schema / query errors#

<field> subtype not found in <Type>#

  • Source: DataFormatValidatorImpl.assignFormatType, thrown when a model field is typed as another model but no FK relationship exists from the owning table to the referenced one.
  • Root cause (99% of cases): The FK constraint is missing from the physical DB. Palmyra reads FKs from JDBC metadata and didn’t find one for this column.
  • Fix: Add the FOREIGN KEY constraint in your schema migration; restart the app. If the FK must be app-managed (shared / read-only schema), declare it via @PalmyraMappingConfig.
  • Related: Schema Discovery explains the lookup path.

field limit not found for filter criteria#

  • Source: Palmyra’s query-param parser seeing a reserved pagination key in the filter slot.
  • Root cause: Client sent limit=N where a column name was expected — typically benign, library noise.
  • Fix: Ignore if it’s just noise. If your application logic relies on filters coming through, sanity-check the client code isn’t accidentally putting pagination controls into the filter map.

Mandatory attribute … must be provided on PUT#

  • Source: DataValidationException during UpdateHandler invocation.
  • Root cause: Palmyra’s update is a full-body replace, not PATCH. A partial request body missing a mandatory = Mandatory.ALL field 500s.
  • Fix: Either send the full object on update, or mark the field less strict (Mandatory.CREATE — mandatory only on create).

Cannot compare left expression of type '<Entity>' with right expression of type 'Long'#

  • Source: Spring Data JPA at bean creation, rejecting a repository method.
  • Root cause: You changed a JPA entity field from Long to @ManyToOne <Entity>. The existing findByFoo(Long) method no longer matches — JPA sees entity.foo as an entity reference, not a scalar.
  • Fix: Rename to findByFooId(Long) — Spring Data resolves "FooId" to entity.foo.id.
  • Related: JPA Coexistence.

500 instead of 401 on UnAuthorizedException#

  • Source: Spring’s default exception handler sees an unhandled UnAuthorizedException from palmyra-dbpwd-mgmt and maps it to 500.
  • Fix: Add an @RestControllerAdvice mapping → 401. Same for EndPointForbiddenException → 403. See Security Configuration Recipes § 4.

IncorrectResultSizeDataAccessException in DevBootstrap#

  • Source: jdbcTemplate.queryForObject("SELECT id FROM …", …).
  • Root cause: Duplicate rows from an earlier buggy seed run. queryForObject expects exactly one row.
  • Fix: SELECT MIN(id) FROM … — tolerates duplicates, picks the earliest.

FK ALTER TABLE fails — “Cannot add or update a child row”#

  • Source: Hibernate / DDL-auto or a manual ALTER adding a FK constraint.
  • Root cause: Existing rows have FK column values that don’t match any row in the referenced table.
  • Fix: Seed the referenced (lookup) table first, then install the FK. Clean up orphan rows if any pre-existing data is genuinely invalid.

Backend — runtime / config errors#

404 on every palmyra endpoint#

  • Source: Spring’s DispatcherServlet.
  • Root cause: Forgot @Import(PalmyraSpringConfiguration.class) on your main class — no autoconfiguration metadata in the jar means the component scan never runs for Palmyra packages.
  • Fix: Add the import.

404 on GET /thing/{id}, works on GET /thing#

  • Source: Handler that implements ReadHandler (or UpdateHandler, DeleteHandler) without a secondaryMapping.
  • Fix: Add secondaryMapping = "/thing/{id}" to the @CrudMapping.

500 + No FileUploadHandler registered for subType: X#

  • Source: tusmgmt extension’s FileUploadHandlerRegistry.resolve.
  • Root cause: No @TusUpload("X") bean in the Spring context.
  • Fix: Add one, or correct the spelling mismatch vs the URL path variable. TUS Uploads integration guide.

406 on TUS PATCH#

  • Source: tus-java-server Content-Type validator.
  • Root cause: Spring’s CharacterEncodingFilter appended ;charset=UTF-8 to application/offset+octet-stream.
  • Fix: server.servlet.encoding.enabled: false in the test/prod profile — or in MockMvc tests, .characterEncoding((String) null).

Stale TUS uploads never clean up#

  • Root cause: @EnableScheduling missing on the main @SpringBootApplication.
  • Fix: Add it.

Frontend — build / runtime errors#

formatFooter is not a function in grid#

  • Source: ColumnConverter.js inside @palmyralabs/rt-forms — expects the GridCustomizer to implement three methods.
  • Root cause: Hand-rolled customizer with only formatCell + formatHeader.
  • Fix: Use the useGridColumnCustomizer(config) helper — it fills in all three automatically.

Grid shows [object Object] in FK columns#

  • Source: Default cell renderer on a nested object.
  • Root cause: Backend started returning nested objects (e.g. patient = {id, externalCode, …}) but the grid column still treats the value as a scalar.
  • Fix: Wire a cellRenderer or a useGridColumnCustomizer enhancer that dot-walks — patient?.externalCode ?? patient?.id ?? ''. Always defensive against both scalar and object shapes.

403 on every mutating request from the SPA#

  • Root cause: CSRF token not present / not echoed. Common causes:
    • Missing withCredentials: true on axios → cookies don’t flow.
    • CsrfTokenRequestHandler still deferred → XSRF-TOKEN cookie never lands on the first GET.
  • Fix: Both sides. Backend: CsrfTokenRequestAttributeHandler with setCsrfRequestAttributeName(null). Frontend: ax.defaults.withCredentials = true in the store factory. See Session Auth wiring and Security Configuration Recipes § 3.

401 redirect loop on first page load#

  • Root cause: RequireAuth probes /user/about on mount, gets 401, redirects to /login, /login renders, user logs in — but something clears the session immediately. Usually a stale service worker or a misconfigured withCredentials.
  • Fix: Check that withCredentials: true is in the store factory’s axiosCustomizer, not just the login call. And verify the Vite proxy is configured — without it, cookies are cross-origin in dev.

DynamicMenu renders empty tree#

  • Root causes (in order of likelihood):
    1. No seed data in xpm_menu / xpm_acl_menu.
    2. The logged-in user’s group has xam.mask = 0 everywhere.
    3. Menu rows have active = 0.
    4. Wrong endpoint — must be /acl/menu/listAll (not /acl/menu).
  • Related: Dynamic Navigation.

Clicking a DynamicMenu item navigates to a 404 route#

  • Root cause: xpm_menu.code doesn’t match any React Router route. The default AsyncTreeMenu click handler calls navigate(row.code).
  • Fix: Either put the route path in code (/patient, not PATIENT), or register alias routes in App.tsx matching the class-style codes.

Test profile gotchas#

MockMvc tests hang or fail randomly#

  • Root cause: ddl-auto: update and testcontainers don’t mix well if the container is reused across test classes.
  • Fix: ddl-auto: create-drop for tests, or use @DirtiesContext to force per-test container cycling.

Basic auth works in CURL but not in MockMvc tests#

  • Root cause: Missing .with(csrf()) on state-changing requests.
  • Fix: Either .with(csrf()) on each request, or disable CSRF in the test security config.

See also#

  • Mental Model — has its own small error → fix table
  • Schema Discovery — for all “field / relation doesn’t resolve” symptoms
  • The integration guides — each extension’s integration-guide page has a Gotchas + Troubleshooting section scoped to that extension