Skip to content

feature: convert models to Java records and modernize the topic service layer - #71

Merged
devin-ai-integration[bot] merged 1 commit into
devin/1786033413-java21-boot3-basefrom
devin/1786033695-records-service
Aug 6, 2026
Merged

devin-ai-integration[bot] merged 1 commit into
devin/1786033413-java21-boot3-basefrom
devin/1786033695-records-service

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Aug 6, 2026 •

Copy link
Copy Markdown

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, Quote and Value become records; ~150 lines of getters/setters/toString disappear. 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:

-public Topic getTopicWithId(String id) {
-    return topics.stream().filter(t -> t.getId().equals(id)).findFirst().get();  // NoSuchElementException -> HTTP 500
-}
+public Optional<Topic> getTopicWithId(String id) {
+    return topics.stream().filter(t -> t.id().equals(id)).findFirst();
+}

and the controller maps empty to 404 via ResponseEntity.notFound(). curl -i /topic/does-not-exist now returns HTTP/1.1 404 instead of a 500.

.collect(Collectors.toList()) → .toList() only where the result is read and discarded (returnAllTopicIDWithStringSlicing, findIdHavingCharacter). The topics field stays a mutable ArrayList and filterMinimumLengthForId still builds a mutable ArrayList, because addTopic/updateTopic/deleteTopic/sortTopicsWithID mutate those lists — .toList() there would compile fine and blow up at runtime with UnsupportedOperationException.

CustomPredicate keeps its identity as a hand-rolled functional interface; it just gains @FunctionalInterface so the compiler enforces that.

SequencedCollection was considered and skipped — nothing in TopicService reads better with getFirst()/reversed() than the current stream code.

Verification

./mvnw clean verify passes on JDK 21, and the app was started with ./mvnw spring-boot:run and 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 /topic and PUT /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, /datetime all behave as before.

The external quote service is dead, so Quote/Value record deserialization could not be verified through the app. It was verified instead by pointing an ObjectMapper at a hand-written sample of the old API shape:

{"type":"success","value":{"id":1,"quote":"..."}}
  -> Quote[type=success, value=Value[id=1, quote=...]]

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 from Customer{id=3, firstName='Josh', ...} to the record-generated Customer[id=3, firstName=Josh, ...]. Log-only, no API impact.

Files not owned by this slice

None needed changing. Application.java, HelloController.java and GreetingController.java construct 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

Status Commit
⚪ Not started —

Run Devin Review

Open in Devin Review (Staging)

…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-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration
devin-ai-integration Bot merged commit cbcfdf7 into devin/1786033413-java21-boot3-base Aug 6, 2026
1 check failed
@devin-ai-integration

Copy link
Copy Markdown
Author

End-to-end runtime testing — 55/55 assertions passed

Ran the app live on JDK 21 (./mvnw spring-boot:run, port 8080, fresh restart) and exercised every endpoint over HTTP. No 500s, no stack traces, no UnsupportedOperationException. (Devin session)

The bug fix: unknown id now 404, was a 500

Seven unknown/odd ids probed — unknown, wrong-case, url-encoded space, 300-char, 0, null, encoded slash. All 404 (encoded slash → 400 from Tomcat, rejected before the controller). No NoSuchElementException in the log.

unknown ids all 404

The mutation risk: interleaved POST → sort → POST → DELETE → sort

The second /topic/sort runs after an append and a removal — exactly where a .toList()-backed immutable list would throw UnsupportedOperationException. It returned 200 with genuinely ascending output.

interleaved mutations

JSON shape is byte-identical to the old POJOs

GET /topic compared by exact string equality against the pre-record literal — key names, order and set all unchanged. Greeting.id still increments per call.

JSON shape

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).

POST record binding

Clean server log + BUILD SUCCESS on JDK 21

0 ERROR lines, 0 stack frames; the only WARN is the expected dead gturnquist-quoters.cfapps.io demo service, and startup succeeded regardless.

log audit
build

Caveats: the repo has no unit tests (No tests to run), so clean verify only proves compilation. Customer, Quote and Value were converted to records but are unreachable via any endpoint, so their JSON shapes were not exercised at runtime (Quote/Value were verified separately with an ObjectMapper against a hand-written sample of the old quote-API payload).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants