11 KiB
11 KiB
Migration Report: Stirling-PDF (Spring Boot 4 → Quarkus)
Status: IN PROGRESS. This is a large, multi-session migration of a ~942-main-file multi-module application. The build layer is complete and validated; the code layer is partially converted (common-module DI). This report is a living document, updated per session.
Summary
- Strategy: Full native Quarkus (not the spring-compat extensions). Chosen after confirming the
quarkus-spring-*shims have no path for theSecurityFilterChain/HttpSecurityDSL, SAML2, OAuth2 login, or MVC interceptors. Native handles these via rewrite (quarkus-oidc,quarkus-smallrye-jwt, JAX-RS filters, Panache). - Agent: claude
- Model: claude-opus-4-8[1m]
- Quarkus version: 3.33.2 LTS
- Java: 25 (kept - Quarkus 3.31+ fully supports JDK 25; needed for jpdfium native FFM)
- SAML2: No native Quarkus extension exists. Rehosted on OpenSAML 5 (already pinned at 5.2.1)
following the dnulnets/quarkus-saml example
(Jakarta
@WebServlet+quarkus-undertow). Not yet implemented in code. - Branch:
migration/run-01(draft-PR workflow; never merged intomain). - Modules completed: 1 of 7 (build). Code module ~partial.
Modules
| Module | Status | Notes |
|---|---|---|
| Build (Gradle + config) | ✅ Done | Plugins, BOM, extension mapping, application.properties. ./gradlew :stirling-pdf:help succeeds. |
| Code: DI/REST/scheduling | 🟡 ~90% of common | common: 69 of 77 Spring files converted and compiling cleanly (DI, scheduling, markers, AppConfig producers, MultipartFile + Resource shims, WebResponseUtils/PDFToFile/FileStorage/ErrorUtils → JAX-RS Response/StreamingOutput). :common:compileJava errors (104) are isolated to the 8 framework-level files below. core/proprietary/saas controllers not started. |
common: 8 remaining files (cross-module-coupled framework pieces)
These cannot be finished within common alone - they ripple into other modules or need new infra, so they are best completed alongside the modules that depend on them:
aop/AutoJobAspect.java+annotations/AutoJobPostMapping.java- Spring AOP@Around+ composed@RequestMapping/@AliasForasync-job framework → CDI@Interceptor+ JAX-RS routing.@AutoJobPostMappingis consumed by controllers in core/proprietary.service/InternalApiClient.java-RestTemplate→java.net.http.HttpClient;post()signature (ResponseEntity<Resource>/MultiValueMap) is called byMcpOperationExecutor/PolicyExecutorin proprietary.service/JobExecutorService.java- depends onFileStorage#storeFromStreamingBodystill typed with SpringStreamingResponseBody.util/GeneralUtils.java- SpringResourceLoader/ResourcePatternUtilsclasspath glob scanning → Quarkus classpath utils.model/ApplicationProperties.java+configuration/YamlPropertySourceFactory.java- SpringEnvironment/PropertySourceFactoryruntime YAML property-source plumbing → SmallRyeConfigSource. | Code: Data JPA → Panache | ⬜ Not started | Repositories/entities across proprietary + saas. | | Code: Security/OAuth2/JWT/SAML | ⬜ Not started | ~200 files. SAML2 on OpenSAML 5; OAuth2→quarkus-oidc; JWT→smallrye-jwt; filters→JAX-RS. | | Frontend (static/SPA routing) | ⬜ Not started | Movestatic/→META-INF/resources/; replaceReactRoutingController. | | Testing → @QuarkusTest | ⬜ Not started | 371 test files. | | Cleanup + verify + PR | ⬜ Not started | |
Changes by area (this run)
Build (committed: 266aed750)
gradle.properties: Quarkus 3.33.2 platform/plugin coordinates.settings.gradle:pluginManagementresolvesio.quarkus; addedmavenCentral().build.gradle(root): removedorg.springframework.boot,io.spring.dependency-management,org.springdoc.openapi-gradle-plugin. Quarkus BOM viaenforcedPlatform. actuator→smallrye-health+micrometer; test→quarkus-junit5+RestAssured;bootRun/bootJar→quarkusDev/quarkusBuild.app/common:webmvc→quarkus-rest+quarkus-undertow(servlet bridge)+hibernate-validator;springdoc→smallrye-openapi; addedquarkus-scheduler,swagger-core-jakarta; kept Jackson 3 (tools.jackson) as a coexisting library.app/core: appliesio.quarkusplugin; removed Jetty/devtools/bootJar; addedquarkus-jdbc-h2.app/proprietary:data-jpa→hibernate-orm-panache;security→quarkus-security; oauth2→quarkus-oidc; mail→quarkus-mailer; cache→quarkus-cache; redis→quarkus-redis-client; SAML2→explicit OpenSAML 5 deps; micrometer-prometheus;quarkus-jdbc-h2/-postgresql.app/saas: security/jpa/oidc/flyway → Quarkus extensions.app/core/.../application.properties: rewritten to Quarkus keys +quarkus.index-dependencyentries for the library modules.
Code: common module (committed: 74474fa96, 5dd6d7be6)
- 20
annotations/api/*markers: removed@RestController/@RequestMapping, kept OpenAPI@Tag, documented the@Patheach controller must now declare directly. SchedulingConfig: removed SpringTaskSchedulerbean (quarkus-scheduler owns the pool).- 17 DI files → CDI:
@Service/@Component→@ApplicationScoped;@Autowired→constructor/@Inject;@Value→@ConfigProperty;@Qualifier→@Named;@Scheduled(fixedRate=…)→@Scheduled(every=…);DisposableBean→@PreDestroy;ApplicationRunner→@Observes StartupEvent; optional@Autowired(required=false)→Instance<>; SpringEnvironment→MicroProfileConfig. AppConfig:@Configuration/@Bean→@Produces/@Named;@Profile("default")→@DefaultBean; request-scoped primitives→@Dependent.
Cross-cutting shims (low-ripple migration aids)
MultipartFile→stirling.software.common.model.MultipartFileinterface mirroring Spring's method surface, withByteArrayMultipartFileandFileUploadMultipartFile(adapts Quarkus RESTFileUpload) implementations. Converting a file is now just an import swap; controllers adapt inboundFileUploadat the boundary. Applied to all 23 common usages.- TODO (next): a similar
Resourcestrategy/shim for the 18org.springframework.core.io.Resourceusages, and a decision onWebResponseUtils(ResponseEntity→ JAX-RSResponse) which ripples into every controller and is best done alongside controller migration.
Established conversion patterns (reusable for remaining modules)
| Spring | Quarkus / Jakarta |
|---|---|
@Service/@Component/@Repository |
@ApplicationScoped |
@Autowired ctor / field |
constructor injection / @Inject |
@Autowired(required=false) |
Instance<T> + isResolvable()/get() |
@Qualifier("x") |
@Named("x") |
@Value("${k:def}") |
@ConfigProperty(name="k", defaultValue="def") |
Environment.getProperty |
Config.getOptionalValue(...) |
@Configuration + @Bean |
bean class + @Produces |
@Bean(name="x") |
@Produces @Named("x") |
@Profile("default") override bean |
@DefaultBean (overridden by proprietary/saas producers) |
@Scope("request") primitive |
@Dependent (see TODO about true request scope) |
@Scheduled(fixedRate=5000) |
io.quarkus.scheduler.@Scheduled(every="5s") |
DisposableBean.destroy() |
@PreDestroy |
ApplicationRunner |
void onStart(@Observes StartupEvent) |
@RestController+@RequestMapping |
JAX-RS @Path on the class (NOT inheritable via meta-annotation) |
Outstanding TODOs (code)
Tracked inline as // TODO: Migration required comments. Major items:
- common:
ApplicationProperties(@ConfigurationProperties→@ConfigMappingor@ConfigProperties),AutoJobAspect(AspectJ→CDI interceptor),RuntimePathConfig/PostHogConfig/cluster@Configurationclasses, 23MultipartFilefiles (→Quarkus multipart /@RestForm), 11ResponseEntityfiles (→JAX-RSResponse), 15core.io.Resourcefiles. - TempFileCleanupService:
@ScheduledSpEL bean-property expression has no Quarkus equivalent; interval must be exposed as a config key. - Jackson: converge 100
tools.jackson(Jackson 3) + 50com.fasterxml(Jackson 2) files; Quarkus REST uses Jackson 2.FAIL_ON_NULL_FOR_PRIMITIVES=falseneeds anObjectMapperCustomizer. - Security (proprietary): full rewrite -
SecurityConfiguration/filters→Quarkus security + JAX-RSContainerRequestFilter; OAuth2→quarkus-oidc; JWT→quarkus-smallrye-jwt; Spring Session→ custom/undertow session; SAML2→OpenSAML 5 servlet. - App entry point:
SPDFApplication(@SpringBootApplication,SpringApplication.run, externalspring.config.additional-location) →@QuarkusMain+ SmallRye Config sources. - OpenAPI: regenerate
SwaggerDoc.jsonfrom/q/openapiif still needed forswaggerhubUpload.
Removed code
| What | Where | Justification |
|---|---|---|
| Spring Boot / dependency-management / springdoc Gradle plugins | root build.gradle |
Replaced by io.quarkus plugin + BOM. |
spring-boot-starter-jetty, jetty-http2/alpn |
core, proprietary | Quarkus uses its own Vert.x HTTP server. |
spring-boot-devtools |
core | Quarkus dev mode (quarkusDev) replaces it. |
spring-session-core |
proprietary | No Quarkus equivalent; session handling to be rewritten (TODO). |
spring-security-saml2-service-provider |
proprietary | Rehosted on OpenSAML 5 (TODO). |
TaskScheduler bean |
SchedulingConfig |
quarkus-scheduler owns the pool. |
Validation (current)
| Check | Result | Notes |
|---|---|---|
Build scripts evaluate (./gradlew :stirling-pdf:help) |
✅ PASS | Quarkus plugin + BOM + extensions resolve. |
compileJava |
❌ Expected FAIL | Code layer mostly still on Spring imports - by design mid-migration. |
| No Spring deps | ❌ Not yet | Spring removed from build; code references remain until ported. |
| Has Quarkus | ✅ PASS | BOM + many extensions present. |
| Tests pass | ⬜ N/A | Test migration not started. |
| Starts up | ⬜ N/A |
Skill improvement suggestions
- The dependency-map should note Quarkus JDK-25 support (3.31+) and the Jackson-2-only integration (Jackson 3 coexistence caveat).
- Add a "multi-module Gradle" recipe: app applies
io.quarkus, libraries indexed viaquarkus.index-dependency(this large-app case isn't covered by the single-module examples). - Add explicit guidance for
@Configuration/@Bean→@Producesincluding@DefaultBeanfor@Profiledefaults and the CDI "can't proxy primitives" limitation vs Spring request scope. - Note that JAX-RS does not honor
@Pathon Spring-style composed/meta-annotations.