Migration to v4
v4 is a breaking release built on Spring Boot 4 / Spring Framework 7. This site documents v4 — if you stay on Spring Boot 3, use the latest 3.5.x release and the GitHub wiki.
Requirements
Section titled “Requirements”| v3.5.x | v4.x | |
|---|---|---|
| Java | 17+ | 17+ |
| Spring Boot | 3.x | 4.x |
| Jackson | 2.x (com.fasterxml.jackson) |
3.x (tools.jackson) |
Jackson 3
Section titled “Jackson 3”Core and databind moved to the tools.jackson.* packages — only the annotations stay at
com.fasterxml.jackson.annotation (as intended by Jackson 3). If you registered custom
serializers for commons types, port them to the new ValueSerializer / ValueDeserializer
base classes.
ErrorResponse follows RFC 9457
Section titled “ErrorResponse follows RFC 9457”Error bodies are now problem details delivered as
application/problem+json. message was renamed to detail; the new optional members
type, title and instance were added. The fields validation map stays as an extension
member:
// before (v3){ "status": 400, "message": "invalid form", "fields": { "email": ["not empty"] } }
// after (v4){ "type": "urn:problem-type:form-error", "title": "Bad Request", "status": 400, "detail": "invalid form", "fields": { "email": ["not empty"] }}Migration aids:
- Java consumers can keep calling the deprecated
getMessage()bridge for a transition period — it delegates todetail. - Inbound payloads with the old
messagekey still deserialize (@JsonAlias). - Recommendation: also enable
spring.mvc.problemdetails.enabled=trueso Spring’s own errors (unknown route, 405, malformed JSON) render as problem+json too.
Nullability annotations
Section titled “Nullability annotations”Switched from org.springframework.lang.Nullable to
JSpecify (org.jspecify.annotations.Nullable) — in line with Spring
Framework 7 itself.
Removed classes & modules
Section titled “Removed classes & modules”| Removed | Replacement |
|---|---|
BaseRestResource, BaseController |
plain RestTemplate/HTTP-interface clients + BasicResponseErrorHandler; shared Api interfaces |
QueryParamBuilder, QueryParamParser, UrlParts |
Spring’s UriComponentsBuilder |
EntityWithKeyValue, HasKeyValue, HasFirstAndLastName model helpers |
model your own types |
commons-rest-posthog module + @PostHogCapture/@PostHogProperty annotations |
use the official PostHog Java SDK directly |
The old v3 deprecation note on BadRequestExceptionHandler (“will get replaced by
javax.ws.rs.BadRequestException”) was dropped — JAX-RS never made sense in a Spring MVC app.
BadRequestException stays a first-class citizen for
checks bean validation can’t express.
Regenerate your TypeScript clients
Section titled “Regenerate your TypeScript clients”Clients generated with a 4.0.0 milestone before M5 do not compile. The folder layout is unchanged —
src/clients, src/hooks and src/model stay where they were — so there is nothing to
restructure. Delete the generated output and generate it again.
Both generation routes wrote cross-folder imports one level too deep: ../../model
instead of ../model, which pointed outside src/ and resolved to nothing. That default is
corrected.
The zip download was broken beyond that. It rendered the templates through its own code
path, which never ran the model generator, so src/model/types.ts and
src/model/zod-schemas.ts were missing from the archive even though model/index.ts
re-exported them. With no generated types to map against, ordinary DTOs came out as Java
names — PageableResult<Activity> was emitted as
io.rocketbase.commons.dto.PageableResult<io.rocketbase.commons.openapi.sample.dto.Activity>.
The endpoint now delegates to generateClientToFileSystem and zips its output, so both
routes produce the same files. That also brings useApi into the zip’s clients/index.ts,
where the file-system route already had it.
Clients built with generateClientToFileSystem (including StandaloneClientGenerator) were
unaffected by that second defect — they always carried their models and resolved type names.
One generated signature changed. React Query v5 infinite hooks for methods that take only
paging parameters now accept the filter their queryOptions counterpart always expected:
// beforeuseInfiniteActivityGetAll(options)// nowuseInfiniteActivityGetAll(filter, options)This needs an endpoint annotated @InfiniteHook whose only parameter is a Pageable — add
any query field of your own and you were already on the correct branch, as
useInfiniteActivityFindAll was. A TypeScript error at the call site tells you which hooks,
if any, are affected.
Checklist
Section titled “Checklist”- Upgrade to Spring Boot 4 first (with your app still on commons-rest 3.5.x it will fail — do both together).
- Bump
io.rocketbase.commons:*to4.0.0. - Replace
com.fasterxml.jackson.databind/coreimports withtools.jackson.*. - Search for
getMessage()calls onErrorResponse— switch togetDetail(). - Update API consumers/tests that assert on the old error JSON (
message→detail). - Enable
spring.mvc.problemdetails.enabled=true. - Regenerate every TypeScript client and adjust paging-only
useInfinite*calls (see above).