Repository navigation
feature: convert models to Java records and modernize the topic service layer - #71
Conversation
…ontroller - convert Topic, Customer, Greeting, Quote, Value to records (component names preserve the existing JSON field names) - add @FunctionalInterface to CustomPredicate - use .toList() where the result is only read; leave the mutable topics list alone - bug: getTopicWithId returns Optional so an unknown id yields 404 instead of 500 Co-Authored-By: alex.vyshetsky <alex.vyshetsky@cognition.ai>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
cbcfdf7
into
devin/1786033413-java21-boot3-base
End-to-end runtime testing — 55/55 assertions passedRan the app live on JDK 21 ( The bug fix: unknown id now 404, was a 500Seven unknown/odd ids probed — unknown, wrong-case, url-encoded space, 300-char, The mutation risk: interleaved POST → sort → POST → DELETE → sortThe second JSON shape is byte-identical to the old POJOs
POST binds JSON to records (no no-arg constructor)Round-tripped with all 3 components populated. Missing field → 200 + nulls; unknown field → 200 + silently dropped (Spring Boot Jackson defaults, unchanged from the POJO version). Clean server log + BUILD SUCCESS on JDK 210 ERROR lines, 0 stack frames; the only WARN is the expected dead Caveats: the repo has no unit tests ( |
Summary
This repo exists to demonstrate language features, so now that it runs on Java 21 the data classes should show Java 21 idioms instead of hand-written Java 8 boilerplate.
Topic,Customer,Greeting,QuoteandValuebecome records; ~150 lines of getters/setters/toStringdisappear. Component names deliberately match the old JSON field names, so every response shape is byte-identical (verified with curl, see below).The one behavioural change is a bug fix:
and the controller maps empty to 404 via
ResponseEntity.notFound().curl -i /topic/does-not-existnow returnsHTTP/1.1 404instead of a 500..collect(Collectors.toList())→.toList()only where the result is read and discarded (returnAllTopicIDWithStringSlicing,findIdHavingCharacter). Thetopicsfield stays a mutableArrayListandfilterMinimumLengthForIdstill builds a mutableArrayList, becauseaddTopic/updateTopic/deleteTopic/sortTopicsWithIDmutate those lists —.toList()there would compile fine and blow up at runtime withUnsupportedOperationException.CustomPredicatekeeps its identity as a hand-rolled functional interface; it just gains@FunctionalInterfaceso the compiler enforces that.SequencedCollectionwas considered and skipped — nothing inTopicServicereads better withgetFirst()/reversed()than the current stream code.Verification
./mvnw clean verifypasses on JDK 21, and the app was started with./mvnw spring-boot:runand exercised end-to-end:GET /topic→[{"id":"spring","subjectName":...,"subjectDescription":...}, ...](unchanged shape)GET /→{"id":1,"content":"Hello, Devin!"}(unchanged shape)GET /topic/does-not-exist→ 404 (was 500)POST /topicandPUT /topic/{id}with JSON bodies → 200, and the topic is correctly created/updated. This is the trap with records (no no-arg constructor); Jackson 2.19 shipped with Boot 3.5 binds records from the canonical constructor, so it works.GET /topic/sort,/topic/minimum/length/{n},DELETE /topic/{id},/topic/string/operation,/topic/file/operation,/datetimeall behave as before.The external quote service is dead, so
Quote/Valuerecord deserialization could not be verified through the app. It was verified instead by pointing anObjectMapperat a hand-written sample of the old API shape:Startup still logs the expected
WARN ... Could not fetch a quote from ...and does not fail.Note:
Customer.toString()output in the startup log changes format fromCustomer{id=3, firstName='Josh', ...}to the record-generatedCustomer[id=3, firstName=Josh, ...]. Log-only, no API impact.Files not owned by this slice
None needed changing.
Application.java,HelloController.javaandGreetingController.javaconstruct models and never call the old getters, so they compile unchanged against the records.Link to Devin session: https://app.devin.ai/sessions/95f05eeb617a4c5e8e76afdf0e9e9757
Devin Review