Lab 04 (Spring Boot / Java Track) β Defect Report
Lab: lab-04-testing-documentation-workflow-java.md
Track affected: π© Spring Boot / Java only (.NET, Kotlin and Swift versions are separate files)
Date: 2026-09-21
Status: Open β lab completed with workarounds, lab document not yet corrected
Related: LAB_01_SPRINGBOOT_DEFECTS.md Β· LAB_02_SPRINGBOOT_DEFECTS.md Β· LAB_03_SPRINGBOOT_DEFECTS.md
Summary
Lab 04 is the shortest lab and has the highest proportion of fabricated content. Every worked
example in Parts 1 and 2 targets a class that does not exist, using construction idioms the real
types cannot support. Part 3 stages directories that were never created, and Part 4's sample PR
description asserts facts about the codebase that are false β including "Breaking Changes: None"
for work that introduces three.
The underlying activities β coverage, documentation, commits, PR prep β are all worthwhile, and
were completed. Almost none of the supplied material could be used.
| # |
Severity |
Defect |
Participant impact |
| 1 |
π΄ Blocking |
/tests and /doc target a non-existent class |
No file to open; both parts have no starting point |
| 2 |
π΄ Blocking |
Samples use builders and setters that cannot exist |
Nothing compiles; records are immutable by design |
| 3 |
π΄ Blocking |
TaskStatus.TODO / DONE again |
Every status assertion fails |
| 4 |
π‘ Moderate |
./mvnw and ./gradlew β neither wrapper exists |
Commands fail immediately |
| 5 |
π‘ Moderate |
Wrong packages throughout |
Imports resolve to nothing |
| 6 |
π‘ Moderate |
Part 3 stages a CQRS layer that was never built |
git add matches nothing |
| 7 |
π‘ Moderate |
README sample documents a different API |
Publishes wrong docs |
| 8 |
π‘ Moderate |
PR sample states false facts about the codebase |
Teaches fabrication as a workflow |
| 9 |
π’ Minor |
Invalid Mockito in a sample test |
Throws at runtime if copied |
| 10 |
π’ Minor |
Asks to create an ADR that already exists |
Confusing, would overwrite |
Defect 1 β /tests and /doc target a non-existent class
Severity: π΄ Blocking
Where
Part 1.1 Step 1 and Part 2.1 Step 1.
What
Open src-springboot/taskmanager-application/src/main/java/com/example/taskmanager/application/service/CreateTaskService.java and select the createTask method.
There is no CreateTaskService. There is no application/service/ directory either β the real
package is application/services/ (plural), and it contains TaskService, a single concrete
class holding all task use cases.
Why it matters
Both Part 1 and Part 2 open with this instruction. A participant cannot begin either part: there
is no file to select, and therefore nothing for /tests or /doc to act on.
Proposed fix
Point at TaskService and pick a concrete method β createTask(String, String, Priority, LocalDateTime) is a good /tests target, and the class-level Javadoc is a good /doc target.
Workaround applied
Ignored the named class. Used JaCoCo to find real gaps instead, which is a better exercise
than /tests on an arbitrary method β see "What replaced Part 1" below.
Defect 2 β Samples use builders and setters that cannot exist
Severity: π΄ Blocking
Where
Part 1.1 Step 3 and Part 1.2, throughout the generated test samples.
What
| Lab sample |
Reality |
Task.builder()...build() |
Task has a private constructor and static factory methods; no builder |
CreateTaskRequest.builder() |
CreateTaskRequest is a record; no builder |
validRequest.setTitle(invalidTitle) |
Records are immutable; no setters |
taskResponse.getTitle() |
Record accessor is title() |
request.getTitle() in the Javadoc sample |
Same |
Task.builder().id(...).status(TaskStatus.TODO) |
Status is assigned by the aggregate, never set externally |
Why it matters
This is not a naming mismatch that a rename fixes. The samples assume a mutable, builder-based
anemic model; the codebase deliberately uses immutable records and an aggregate that protects its
own invariants. Following the lab means undoing three labs' worth of design.
The setter samples are the clearest signal: validRequest.setTitle(invalidTitle) mutates a shared
fixture across parameterized runs, which is exactly the pattern the record-based design prevents.
Proposed fix
Regenerate the samples against the real API: Task.create(title, description, priority, dueDate),
new CreateTaskRequest(title, description, priority, dueDate), and per-case construction instead
of fixture mutation.
Defect 3 β TaskStatus.TODO / DONE again
Severity: π΄ Blocking
Where
Part 1.1 (TaskStatus.TODO in three places), Part 1.2 (jsonPath("$.status").value("TODO")),
Part 2.2 (README sample lists TODO, IN_PROGRESS, DONE, CANCELLED).
What
The enum is PENDING, IN_PROGRESS, COMPLETED, CANCELLED.
Why it matters
Identical to Lab 03 Defect 3, now propagated into documentation the lab tells participants to
publish. A reader of the resulting README would be told the API accepts values it rejects.
Confirmed against the running application:
GET /api/tasks?status=DONE
400 : Invalid status: DONE. Valid values: [PENDING, IN_PROGRESS, COMPLETED, CANCELLED]
Proposed fix
Correct in all five locations. Given this is the second lab carrying the same error, the value
list is worth extracting into a single shared snippet.
Defect 4 β ./mvnw and ./gradlew β neither wrapper exists
Severity: π‘ Moderate
Where
Part 1.3 and the PR sample's "Testing Performed" section.
What
Or if using Gradle:
src-springboot/ contains no Maven wrapper (mvnw, mvnw.cmd, .mvn/) and is not a Gradle
project. Every other lab uses plain mvn.
Why it matters
The command fails instantly with "command not found". Offering a Gradle alternative also implies
a choice that does not exist β the Spring Boot track is Maven-only.
Proposed fix
Use mvn clean test, matching Labs 1β3. Drop the Gradle alternative, or add a genuine wrapper via
mvn wrapper:wrapper if wrapper usage is the intended lesson.
Defect 5 β Wrong packages throughout
Severity: π‘ Moderate
Where
Every import block in Parts 1 and 2.
What
| Lab |
Reality |
com.example.taskmanager.domain.entity |
...domain.tasks |
com.example.taskmanager.domain.repository |
...domain.tasks |
com.example.taskmanager.domain.valueobject |
...domain.tasks |
com.example.taskmanager.application.dto |
...api.dto |
com.example.taskmanager.application.service |
...application.services |
Why it matters
The domain module is feature-oriented β TaskId, TaskStatus, Priority, Task and
TaskRepository all live in domain.tasks, as the repository instructions require. The lab
assumes technical grouping, which is the layout those instructions explicitly discourage.
Note this also contradicts Lab 02, which placed DTOs in application.dto β neither matches the
repository, and they do not match each other.
Defect 6 β Part 3 stages a CQRS layer that was never built
Severity: π‘ Moderate
Where
Part 3.1 and the Part 3.2 sample commit message.
What
git add src-springboot/taskmanager-application/src/main/java/com/example/taskmanager/application/query/
There is no query/ package. The sample commit message then describes work nobody did:
- Implement GetTasksQuery and GetTaskByIdQuery handlers
No such classes exist. The repository has a single concrete TaskService, and
springboot.instructions.md explicitly says "no CQRS libraries for this workshop".
The second sample, for LegacyTaskProcessor, is fabricated in a subtler way:
- Remove nested for loops and replace with Stream API
Reduces cyclomatic complexity from 15 to 4 per method.
The real refactor uses a StringBuilder loop, not the Stream API β a per-character transformation
is one of the few places a plain loop genuinely reads better. And the complexity figures are
invented; nothing measured them.
Why it matters
git add on a non-existent path silently matches nothing, so a participant commits an empty
staging area or, worse, does not notice. More seriously, the lab is teaching people to write
commit messages describing work they did not do, with metrics they did not measure. That is the
opposite of the lesson.
Proposed fix
Stage real paths. Replace the fabricated bullets with a note that specifics and metrics must come
from the actual diff β and that inventing them is the failure mode to avoid when an assistant
drafts your commit message.
Workaround applied
Wrote Conventional Commits describing the real changes, with ! markers and BREAKING CHANGE:
footers for all three breaking changes.
Defect 7 β README sample documents a different API
Severity: π‘ Moderate
Where
Part 2.2, "Generate API Documentation (README)".
What
The sample README section documents:
GET /api/tasks?completed=true β there is no completed filter; filters are priority,
status, sort, direction, page, size
- A bare array response β
GET /api/tasks returns a paged envelope
PUT /api/tasks/{id} β the real update verb is PATCH
- Status values
TODO / DONE β see Defect 3
- No mention of
GET /api/tasks/due-soon
- A create example without
priority, which now returns 400
Why it matters
Part 2 instructs participants to add this to README.md. Following it publishes documentation
that is wrong in six ways for an API sitting in the same repository.
There is a related trap: src-springboot/README.md already had an API Endpoints section, so
the instruction to "create an API documentation section" produces a duplicate rather than
updating the stale one.
Workaround applied
Rewrote the existing section in src-springboot/README.md to match reality β all nine endpoints,
the query-parameter table, the paged envelope, real field values, the required priority, and the
RFC 7807 error shapes.
Defect 8 β PR sample states false facts about the codebase
Severity: π‘ Moderate
Where
Part 4.1, "Expected Output".
What
| Sample claim |
Reality |
| "CQRS pattern for commands and queries" |
No CQRS; instructions forbid it |
| "Unit Tests: 45+ tests" |
305 |
| "Coverage: ~92% code coverage" |
Never measured before this lab; nothing produced that figure |
| "Average response time: <50ms", "No memory leaks detected in load testing" |
No load testing was performed |
| "Breaking Changes: β οΈ None" |
Three, all in the work these labs produce |
| "Migration Required: None β Uses in-memory H2" |
Flyway migrations are required; production runs ddl-auto: validate |
| "Stream API instead of nested loops" |
StringBuilder loop |
Why it matters
This is the most damaging defect in the lab, because it is the template participants are told to
reuse. It models inventing test counts, coverage percentages and performance figures, and β worst
β asserting "no breaking changes" on work whose headline change is that POST /api/tasks now
rejects previously valid requests.
A reviewer trusting that line would approve a contract break.
Proposed fix
Replace with a PR description drawn from the actual diff, and add an explicit warning: an
assistant will happily invent coverage numbers and performance claims, so every factual assertion
in a generated PR description must be verified before posting.
Workaround applied
Wrote a PR description with real counts, a prominent β οΈ Breaking changes section listing all
three, a Known Gaps section, and a reviewer checklist that asks whether the breaks are acceptable.
Defect 9 β Invalid Mockito in a sample test
Severity: π’ Minor
Where
Part 1.1, createTask_LogsInformationMessages.
What
when(taskRepository.save(any(Task.class))).thenReturn(any(Task.class));
An argument matcher is used as a return value. Mockito throws
InvalidUseOfMatchersException at runtime.
The neighbouring createTask_SetsCorrectDefaultStatus also declares
Task capturedTask = Task.builder().build(); and never uses it.
Why it matters
Minor, but it is in a sample presented as Copilot's "comprehensive" output, and it fails in a way
that is confusing to diagnose β the error surfaces on an unrelated subsequent stubbing.
Proposed fix
thenAnswer(invocation -> invocation.getArgument(0)), and delete the unused variable.
Defect 10 β Asks to create an ADR that already exists
Severity: π’ Minor
Where
Part 2.3, "Generate Architecture Documentation (ADR)".
What
Create an Architecture Decision Record (ADR) in docs/adr/0001-use-clean-architecture.md
0001-use-clean-architecture.md already exists and is
referenced by the architecture docs.
Proposed fix
Ask for an ADR covering a decision the participant actually made during the labs, at the next free
number. That is a genuinely better exercise: the decision is fresh and the tradeoffs are real.
Workaround applied
Wrote ADR 0003 on paging in the
application layer, following ADR 0002 from Lab 02.
Verification status of the reference implementation
cd src-springboot && mvn clean test β BUILD SUCCESS
| Module |
Tests |
Before Lab 4 |
After |
| taskmanager-domain |
76 |
65 |
+11 |
| taskmanager-application |
123 |
106 |
+17 |
| taskmanager-infrastructure |
52 |
48 |
+4 |
| taskmanager-api |
54 |
54 |
β |
| Total |
305 |
273 |
+32 |
What replaced Part 1
Rather than running /tests on an arbitrary method, JaCoCo was used to find classes with genuinely
untested logic. Four stood out, and all four are now fully covered:
| Class |
Before |
After |
Gap that was hiding there |
TaskSortField |
0% |
100% |
Comparators were only exercised indirectly through API tests |
TaskProcessingException |
0% |
100% |
Never constructed β the failure path was untested |
FileTaskOutputWriter |
69% |
100% |
The IO failure path was untested |
TaskId |
50% |
100% |
TaskId.of(String) and its malformed-input rejection |
The TaskSortField gap is the instructive one: the class had zero unit coverage despite its
behaviour being asserted by API integration tests. Per-module coverage made that visible, and the
nulls-last ordering rule β the subtlest logic in the class β now has direct tests in both
directions.
This is a better exercise than the lab's, and worth adopting: measure first, then write the
tests the measurement justifies.
What replaced Part 2
- Rewrote the stale API section of
src-springboot/README.md
- Added ADR 0003
Deliberate deviations
- Coverage-driven test selection instead of
/tests on a named method β see above.
- No Javadoc sweep. The classes written across Labs 1β3 already carry Javadoc on public
members. A blanket /doc pass would have generated restatements of method signatures, which
the repo instructions explicitly discourage.
Suggested triage
Before the next workshop run β Defects 1β3. Neither Part 1 nor Part 2 can be started.
Soon after β Defects 4β8. Defect 8 is the priority within this group despite its severity
rating: the PR template teaches participants to assert things they have not verified, and it
denies breaking changes that exist.
When convenient β Defects 9β10.
Lab 04 (Spring Boot / Java Track) β Defect Report
Lab: lab-04-testing-documentation-workflow-java.md
Track affected: π© Spring Boot / Java only (.NET, Kotlin and Swift versions are separate files)
Date: 2026-09-21
Status: Open β lab completed with workarounds, lab document not yet corrected
Related: LAB_01_SPRINGBOOT_DEFECTS.md Β· LAB_02_SPRINGBOOT_DEFECTS.md Β· LAB_03_SPRINGBOOT_DEFECTS.md
Summary
Lab 04 is the shortest lab and has the highest proportion of fabricated content. Every worked
example in Parts 1 and 2 targets a class that does not exist, using construction idioms the real
types cannot support. Part 3 stages directories that were never created, and Part 4's sample PR
description asserts facts about the codebase that are false β including "Breaking Changes: None"
for work that introduces three.
The underlying activities β coverage, documentation, commits, PR prep β are all worthwhile, and
were completed. Almost none of the supplied material could be used.
/testsand/doctarget a non-existent classTaskStatus.TODO/DONEagain./mvnwand./gradlewβ neither wrapper existsgit addmatches nothingDefect 1 β
/testsand/doctarget a non-existent classSeverity: π΄ Blocking
Where
Part 1.1 Step 1 and Part 2.1 Step 1.
What
There is no
CreateTaskService. There is noapplication/service/directory either β the realpackage is
application/services/(plural), and it containsTaskService, a single concreteclass holding all task use cases.
Why it matters
Both Part 1 and Part 2 open with this instruction. A participant cannot begin either part: there
is no file to select, and therefore nothing for
/testsor/docto act on.Proposed fix
Point at
TaskServiceand pick a concrete method βcreateTask(String, String, Priority, LocalDateTime)is a good/teststarget, and the class-level Javadoc is a good/doctarget.Workaround applied
Ignored the named class. Used JaCoCo to find real gaps instead, which is a better exercise
than
/testson an arbitrary method β see "What replaced Part 1" below.Defect 2 β Samples use builders and setters that cannot exist
Severity: π΄ Blocking
Where
Part 1.1 Step 3 and Part 1.2, throughout the generated test samples.
What
Task.builder()...build()Taskhas a private constructor and static factory methods; no builderCreateTaskRequest.builder()CreateTaskRequestis a record; no buildervalidRequest.setTitle(invalidTitle)taskResponse.getTitle()title()request.getTitle()in the Javadoc sampleTask.builder().id(...).status(TaskStatus.TODO)Why it matters
This is not a naming mismatch that a rename fixes. The samples assume a mutable, builder-based
anemic model; the codebase deliberately uses immutable records and an aggregate that protects its
own invariants. Following the lab means undoing three labs' worth of design.
The setter samples are the clearest signal:
validRequest.setTitle(invalidTitle)mutates a sharedfixture across parameterized runs, which is exactly the pattern the record-based design prevents.
Proposed fix
Regenerate the samples against the real API:
Task.create(title, description, priority, dueDate),new CreateTaskRequest(title, description, priority, dueDate), and per-case construction insteadof fixture mutation.
Defect 3 β
TaskStatus.TODO/DONEagainSeverity: π΄ Blocking
Where
Part 1.1 (
TaskStatus.TODOin three places), Part 1.2 (jsonPath("$.status").value("TODO")),Part 2.2 (README sample lists
TODO, IN_PROGRESS, DONE, CANCELLED).What
The enum is
PENDING, IN_PROGRESS, COMPLETED, CANCELLED.Why it matters
Identical to Lab 03 Defect 3, now propagated into documentation the lab tells participants to
publish. A reader of the resulting README would be told the API accepts values it rejects.
Confirmed against the running application:
Proposed fix
Correct in all five locations. Given this is the second lab carrying the same error, the value
list is worth extracting into a single shared snippet.
Defect 4 β
./mvnwand./gradlewβ neither wrapper existsSeverity: π‘ Moderate
Where
Part 1.3 and the PR sample's "Testing Performed" section.
What
./mvnw test./gradlew testsrc-springboot/contains no Maven wrapper (mvnw,mvnw.cmd,.mvn/) and is not a Gradleproject. Every other lab uses plain
mvn.Why it matters
The command fails instantly with "command not found". Offering a Gradle alternative also implies
a choice that does not exist β the Spring Boot track is Maven-only.
Proposed fix
Use
mvn clean test, matching Labs 1β3. Drop the Gradle alternative, or add a genuine wrapper viamvn wrapper:wrapperif wrapper usage is the intended lesson.Defect 5 β Wrong packages throughout
Severity: π‘ Moderate
Where
Every import block in Parts 1 and 2.
What
com.example.taskmanager.domain.entity...domain.taskscom.example.taskmanager.domain.repository...domain.taskscom.example.taskmanager.domain.valueobject...domain.taskscom.example.taskmanager.application.dto...api.dtocom.example.taskmanager.application.service...application.servicesWhy it matters
The domain module is feature-oriented β
TaskId,TaskStatus,Priority,TaskandTaskRepositoryall live indomain.tasks, as the repository instructions require. The labassumes technical grouping, which is the layout those instructions explicitly discourage.
Note this also contradicts Lab 02, which placed DTOs in
application.dtoβ neither matches therepository, and they do not match each other.
Defect 6 β Part 3 stages a CQRS layer that was never built
Severity: π‘ Moderate
Where
Part 3.1 and the Part 3.2 sample commit message.
What
There is no
query/package. The sample commit message then describes work nobody did:No such classes exist. The repository has a single concrete
TaskService, andspringboot.instructions.mdexplicitly says "no CQRS libraries for this workshop".The second sample, for
LegacyTaskProcessor, is fabricated in a subtler way:The real refactor uses a
StringBuilderloop, not the Stream API β a per-character transformationis one of the few places a plain loop genuinely reads better. And the complexity figures are
invented; nothing measured them.
Why it matters
git addon a non-existent path silently matches nothing, so a participant commits an emptystaging area or, worse, does not notice. More seriously, the lab is teaching people to write
commit messages describing work they did not do, with metrics they did not measure. That is the
opposite of the lesson.
Proposed fix
Stage real paths. Replace the fabricated bullets with a note that specifics and metrics must come
from the actual diff β and that inventing them is the failure mode to avoid when an assistant
drafts your commit message.
Workaround applied
Wrote Conventional Commits describing the real changes, with
!markers andBREAKING CHANGE:footers for all three breaking changes.
Defect 7 β README sample documents a different API
Severity: π‘ Moderate
Where
Part 2.2, "Generate API Documentation (README)".
What
The sample README section documents:
GET /api/tasks?completed=trueβ there is nocompletedfilter; filters arepriority,status,sort,direction,page,sizeGET /api/tasksreturns a paged envelopePUT /api/tasks/{id}β the real update verb isPATCHTODO/DONEβ see Defect 3GET /api/tasks/due-soonpriority, which now returns 400Why it matters
Part 2 instructs participants to add this to
README.md. Following it publishes documentationthat is wrong in six ways for an API sitting in the same repository.
There is a related trap:
src-springboot/README.mdalready had an API Endpoints section, sothe instruction to "create an API documentation section" produces a duplicate rather than
updating the stale one.
Workaround applied
Rewrote the existing section in
src-springboot/README.mdto match reality β all nine endpoints,the query-parameter table, the paged envelope, real field values, the required
priority, and theRFC 7807 error shapes.
Defect 8 β PR sample states false facts about the codebase
Severity: π‘ Moderate
Where
Part 4.1, "Expected Output".
What
ddl-auto: validateStringBuilderloopWhy it matters
This is the most damaging defect in the lab, because it is the template participants are told to
reuse. It models inventing test counts, coverage percentages and performance figures, and β worst
β asserting "no breaking changes" on work whose headline change is that
POST /api/tasksnowrejects previously valid requests.
A reviewer trusting that line would approve a contract break.
Proposed fix
Replace with a PR description drawn from the actual diff, and add an explicit warning: an
assistant will happily invent coverage numbers and performance claims, so every factual assertion
in a generated PR description must be verified before posting.
Workaround applied
Wrote a PR description with real counts, a prominentβ οΈ Breaking changes section listing all
three, a Known Gaps section, and a reviewer checklist that asks whether the breaks are acceptable.
Defect 9 β Invalid Mockito in a sample test
Severity: π’ Minor
Where
Part 1.1,
createTask_LogsInformationMessages.What
An argument matcher is used as a return value. Mockito throws
InvalidUseOfMatchersExceptionat runtime.The neighbouring
createTask_SetsCorrectDefaultStatusalso declaresTask capturedTask = Task.builder().build();and never uses it.Why it matters
Minor, but it is in a sample presented as Copilot's "comprehensive" output, and it fails in a way
that is confusing to diagnose β the error surfaces on an unrelated subsequent stubbing.
Proposed fix
thenAnswer(invocation -> invocation.getArgument(0)), and delete the unused variable.Defect 10 β Asks to create an ADR that already exists
Severity: π’ Minor
Where
Part 2.3, "Generate Architecture Documentation (ADR)".
What
0001-use-clean-architecture.md already exists and is
referenced by the architecture docs.
Proposed fix
Ask for an ADR covering a decision the participant actually made during the labs, at the next free
number. That is a genuinely better exercise: the decision is fresh and the tradeoffs are real.
Workaround applied
Wrote ADR 0003 on paging in the
application layer, following ADR 0002 from Lab 02.
Verification status of the reference implementation
cd src-springboot && mvn clean testβ BUILD SUCCESSWhat replaced Part 1
Rather than running
/testson an arbitrary method, JaCoCo was used to find classes with genuinelyuntested logic. Four stood out, and all four are now fully covered:
TaskSortFieldTaskProcessingExceptionFileTaskOutputWriterTaskIdTaskId.of(String)and its malformed-input rejectionThe
TaskSortFieldgap is the instructive one: the class had zero unit coverage despite itsbehaviour being asserted by API integration tests. Per-module coverage made that visible, and the
nulls-last ordering rule β the subtlest logic in the class β now has direct tests in both
directions.
This is a better exercise than the lab's, and worth adopting: measure first, then write the
tests the measurement justifies.
What replaced Part 2
src-springboot/README.mdDeliberate deviations
/testson a named method β see above.members. A blanket
/docpass would have generated restatements of method signatures, whichthe repo instructions explicitly discourage.
Suggested triage
Before the next workshop run β Defects 1β3. Neither Part 1 nor Part 2 can be started.
Soon after β Defects 4β8. Defect 8 is the priority within this group despite its severity
rating: the PR template teaches participants to assert things they have not verified, and it
denies breaking changes that exist.
When convenient β Defects 9β10.