Claude Code for
Spring Developers

Thomas Schilling · tschuehly.de

Spring I/O 2026 · Barcelona

Thomas Schilling speaking at Spring I/O 2023

⬇ Add me on LinkedIn

LinkedIn QR Code

Thomas Schilling

@tschuehly · tschuehly.de · Stuttgart

  • Founder — PhotoQuest
  • Spring Boot + Kotlin + HTMX = ❤️
  • OSS: Spring ViewComponent
  • Speaker: Spring I/O '23 & '24, JavaZone, W-JAX, Devoxx BE
  • Open to new opportunities
GitHub contribution trend
3,700+ commits
1,700+ sessions
11k prompts sent
5x commit velocity

Meet Your New Teammate

1
Intro
2
Basics
3
Context
4
Skills
5
Hooks
6
MCP
7
Workflows
8
Tips
9
Outlook

The Demo Project

A photo reaction game on a beamer. The manager starts a round, guests join via QR code. Guests see the photos on the beamer and spend their reaction budget (3 ❤️ + 3 😂 + 3 😮) during a timer. Top 3 photos per category are revealed at the end.
Guests join via QR code
Top 3 photos per category reveal

The Agentic Loop

An agentic coding tool that operates in a loop:

Read Plan Act Observe Repeat

▐▛███▜▌   Opus 4.6 · Claude Max
▝▜█████▛▘
▘▘ ▝▝    ~/PhotoQuest
The RankingGameService.createSession method is missing input validation for the timer duration. Add validation that the duration must be between 1 and 30 minutes
Searched for 1 pattern, read 1 file
Now I'll look at the existing exceptions to follow the pattern:
Searched for 1 pattern, read 1 file
⏺ Update(…/core/rankinggame/RankingGameService.kt)
Added 5 lines
  class InvalidTimerDurationException : PhotoQuestException(
      "Die Timer-Dauer muss zwischen 1 und 30 Minuten liegen.",
      "Timer duration must be between 1 and 30 minutes"
  )
⏺ Update(…/core/rankinggame/RankingGameService.kt)
Added 1 line
  if (timerSeconds < 60 || timerSeconds > 1800) throw InvalidTimerDurationException()
Now let me compile to verify:
Ran 1 bash command

Compilation succeeds. Validation runs before the advisory lock — invalid requests fail early without acquiring any locks.
✻ Cogitated for 1m 24s

Pick Your Interface

  • Terminal (CLI)
  • VS Code & IntelliJ
  • Desktop app
  • Web app
  • Smartphone app
  • Slack & GitHub Actions

Your Fastest Coworker

  • Design your architecture together
  • Challenge your assumptions
  • Create a plan, then implement it
  • Build features & fix bugs
  • Design your UI
  • Write your tests

The Spring Advantage

  • Conventions are strong
  • Compile-time verification
  • Testability is built-in
  • Structure is predictable

Your stack is your advantage.

Pick Your Brain

Sonnet
  • Fast iteration
  • Simple fixes
  • High-volume tasks
  • Cheap subagents
Opus
  • Complex architecture
  • Multi-file refactors
  • Planning
  • Debugging
/effort low Quick lookups
/effort medium Straightforward tasks
/effort high Planning & specs
/effort max Hard debugging

Your Safety Net

Checkpoints

Wrong direction? /rewind. Want to branch? /branch.

Experimentation is free.

▐▛███▜▌   Opus 4.6 · Claude Max
▝▜█████▛▘
▘▘ ▝▝    ~/PhotoQuest
refactor RankingGameService to use a sealed interface for game state instead of the enum
⏺ Update(…/rankinggame/RankingGameService.kt) Added 19 lines
⏺ Update(…/rankinggame/RankingGameService.kt) Added 2 lines, removed 1 line
⏺ Update(…/rankinggame/RankingGameService.kt) Added 5 lines, removed 8 lines
⏺ Update(…/rankinggame/RankingGameService.kt) Added 11 lines, removed 14 lines
Refactor react:
Interrupted · What should Claude do instead?
/rewind

Rewind
Restore the code and/or conversation to the point before…

  ❯ refactor RankingGameService to use a sealed interface…
    RankingGameService.kt +37 -23

    (current)

Enter to continue · Esc to exit

Delegate Without Losing Focus

Separate context window.

Your session stays lean.

"Research X with a subagent"

▐▛███▜▌   Opus 4.6 · Claude Max
▝▜█████▛▘
▘▘ ▝▝    ~/PhotoQuest
Use a subagent to analyze how the SSE notification system works in this project, then summarize the findings
⏺ Explore(Analyze SSE notification system)
  Done (49 tool uses · 84.4k tokens · 1m 33s)
SSE Notification System Overview

The project uses PostgreSQL LISTEN/NOTIFY as the event transport, with Spring WebFlux Flux streams delivering events to browsers.

1. Slideshow Updates (live photo feed)
  Guest uploads photo → pg_notify → SlideshowNotificationService → Reactor Sink → SSE stream

2. Ranking Game (live multiplayer game events)
  Game action → pg_notify('ranking_game_$sessionId') → per-session listener → SSE stream

Architecture:
  Browser (EventSource / HTMX)
    ↕ HTTP SSE stream
  Spring Controller (Flux<ServerSentEvent>)
    ↕ Reactor Sink
  NotificationService (listener thread)
    ↕ JDBC polling pgConn.getNotifications(500ms)
  PostgreSQL LISTEN/NOTIFY
    ↑ pg_notify() called by business services

No UI, No Problem

claude -p "query" — headless mode for scripting

@claude in PR comments — GitHub App triggers a GitHub Actions workflow

tschuehly@Mac PhotoQuest % claude -p --model haiku "What database tables does the ranking game feature use?"
The ranking game feature uses three main database tables:

ranking_game_session
Main session table — id, event_id, status (LOBBY/ACTIVE/FINISHED/REVEAL),
timer_duration_seconds, budget_heart/funny/wow, timestamps

ranking_game_participant
Tracks which guests are playing — session_id, event_guest_id, joined_at
Unique constraint: one participant per guest per session

ranking_game_reaction
Records each reaction — participant_id, submission_id, reaction_type (HEART/FUNNY/WOW)
Unique constraint: one reaction per participant per photo per session

The Vibe Coding Baseline

Iteration 1: no config, no skills, no plan

❯ A photo reaction game on a beamer. The manager starts a round, guests join via QR code. Guests see the photos on the beamer and spend their reaction budget (3 ❤️ + 3 😂 + 3 😮) during a timer. Top 3 photos per category are revealed at the end.

No tests written.

8 bugs I had to find by hand.

The whole REVEAL state skipped.

▐▛███▜▌   Opus 4.6 · Claude Max
▝▜█████▛▘
▘▘ ▝▝    ~/PhotoQuest.wt-demo-round1
A photo reaction game on a beamer. The manager starts a round, guests join via QR code. Guests see the photos on the beamer and spend their reaction budget (3 ❤️ + 3 😂 + 3 😮) during a timer. Top 3 photos per category are revealed at the end.
When joining the game it shows {JSON…} instead of the rendered html
The images overlap and are all shown at the same time and the game doesn't autostart for the participants
There is no result at the end. I voted but it says "No votes"
  … 5 more bug reports from me, back-to-back

What if Claude had knowledge, skills, and a plan?

Every Session Is Day One

A new deveveloper onboards once.

Claude onboards every session.

Context is how you onboard.

1
Intro
2
Basics
3
Context
4
Skills
5
Hooks
6
MCP
7
Workflows
8
Tips
9
Outlook

The Cost of Missing Context

"Add a download pop-up for digital-only subscribers"
— from my phone, no plan mode

Claude hardcoded 6 cards per A4 sheet.

Reality: 1 card per A6 page.

The agent only knows what's in context.

The Onboarding Doc

CLAUDE.md

1 Start with 5 lines

2 Use Claude Code → observe what goes wrong

3 Undesired behaviour → add a rule

4 Repeat

Every rule needs to earn its place through a real failure.

Inside my CLAUDE.md

# Critical Rules
- Compile after .kt changes:
  ./gradlew compileKotlin compileTestKotlin
- Verify before done: never mark a task complete without
  proving it works (run tests, demonstrate correctness)
- Multi-instance safe: no in-memory state for dedup/guards;
  use DB checks. AdvisoryLock.kt for distributed consensus

Scoped Rules, Clean Context

.claude/rules/

.claude/rules/
├── kotlin-conventions.md    → src/main/kotlin/**
├── viewcomponent.md         → src/main/kotlin/page/**
├── tailwind-daisyui.md      → src/**/*.html
├── testing.md               → src/test/**
└── payment.md               → src/**/payment/**

Each file loads only when Claude touches matching paths.

Claude Remembers

Auto-memory — local, not in git

Claude takes notes across sessions — stored locally, not in git

CLAUDE.md➡️most important information .claude/rules/➡️path-specific conventions Auto-memory➡️personal

/context — Your Context Budget

/context shows where you're spending it.

/compact manual compression.

Fresh Session, Fresh Attention

Why I don't use /compact

Performance degrades as context fills up

Resuming a stale session re-reads everything uncached

  • Track state in markdown plan files
  • Wrap up the session manually
  • Write a continuation prompt

The Documentation You
Should Have Written

If a senior dev wrote down how they approach architecture, git flow, and domain knowledge — that's a skill.

Working with an agent forces you to write it down.

1
Intro
2
Basics
3
Context
4
Skills
5
Hooks
6
MCP
7
Workflows
8
Tips
9
Outlook

Always-On vs On-Demand

CLAUDE.mdSkill
LoadsOnce at session startDescriptions at start, full content when invoked
Best for"Always do X" rulesReference docs, repeatable workflows
Context costAlways present, drifts from attentionFresh context at moment of action

Skills load fresh context at the moment of action.

My Core Skills

/interview AI interviews YOU before implementation

/tdd-task Strict TDD: RED → GREEN → REFACTOR

/test Run tests, report failures only

/commit Create feature commits

8 Lines, Better Commits

.claude/skills/commit/SKILL.md

---
name: commit
description: Group uncommitted changes into feature commits.
---
1. Run `git status` and `git diff` to understand all changes
2. Group changed files by logical feature
3. Stage and commit each group with action-oriented message
4. Repeat until all changes are committed
"If an AI generates code from a spec, the spec is now the highest-leverage artifact for catching errors."
— Thoughtworks, Deer Valley 2026

Rock solid specs with /interview

▐▛███▜▌   Opus 4.6 · Claude Max
▝▜█████▛▘
▘▘ ▝▝    ~/PhotoQuest
/interview A photo reaction game on a beamer. The manager starts a round, guests join via QR code…

Let me explore the codebase first to understand the existing architecture before interviewing you.

⏺ Explore(Explore codebase architecture)
  ⎿ Done (53 tool uses · 104.6k tokens · 1m 47s)
Good, I have a solid understanding of the codebase. Let me start the interview.

←  ☐ Entry point  ☐ Photo source  ☐ Guest join  ✔ Submit  →

Where does the manager find the Ranking Game?…
1. Manager dashboard (/admin)
   Add a 'Start Ranking Game' button…
2. Event detail page
   Add it as a new section/tab…
3. Dedicated /ranking-game route
   New standalone page…
❯ 4. Dock Link in QuestComponent
Enter to select · Tab/Arrow keys to navigate · Esc to cancel
▐▛███▜▌   Opus 4.6 · Claude Max
▝▜█████▛▘
▘▘ ▝▝    ~/PhotoQuest

←  ☒ Anti-cheat  ☐ Undo reactions  ✔ Submit  →

Security: should we prevent reaction manipulation?…
1. Server-side budget enforcement
   DB tracks reactions per guest…
2. Client-side only
   Track budget in JS/Alpine state…
❯ 3. Client side with a cookie
Enter to select · Tab/Arrow keys to navigate · Esc to cancel

←  ☐ Storage model  ☐ Min photos  ✔ Submit  →

Clarifying: reactions stored in the DB, but budget tracked client-side via a cookie?…
1. DB reactions, cookie budget
   Each reaction is a DB row. Budget is client-side…
2. All in cookie/localStorage
   Reactions stored only in browser…
3. Server-side budget enforcement
   Enforce budget per cookie_token…
❯ 4. Let's do DB enforcement. Each reaction tied to event_guest row
Enter to select · Tab/Arrow keys to navigate · Esc to cancel
▐▛███▜▌   Opus 4.6 · Claude Max
▝▜█████▛▘
▘▘ ▝▝    ~/PhotoQuest

←  ☐ React UX  ☐ QR persistence  ✔ Submit  →

For the guest phone photo grid: how should reaction buttons appear? As an overlay on each photo thumbnail, or in a detail view when tapping a photo?
1. Overlay on thumbnail          +---------------------------------------+
   2. Tap to enlarge + react        | +--------+ +--------+                |
                                    | | Photo  | | Photo  |                |
                                    | +--------+ +--------+                |
                                    |   ❤️ 😂 😮    ❤️ 😂 😮                 |
                                    | +--------+ +--------+                |
                                    | | Photo  | | Photo  |                |
                                    | +--------+ +--------+                |
                                    |   ❤️ 😂 😮    ❤️ 😂 😮                 |
                                    +--------------------------------------+
    1. Overlay on thumbnail         +--------------------------------------+
  2. Tap to enlarge + react      | +------------------+                  |
                                    | |                  |                 |
                                    | |   Photo (big)    |                 |
                                    | |                  |                 |
                                    | +------------------+                 |
                                    |    [❤️]  [😂]  [😮]                   |
                                    |    < prev | next >                   |
                                    +--------------------------------------+

8 rounds → 20+ design decisions → 171-line spec

How I Review a Spec

## Reactions
Each guest gets 3 ❤️ + 3 😂 + 3 😮
% confirm this is the correct cookie name
% can we prompt user to enable fullscreen?

Claude suggested 10 changes

Forced to justify: 8 were unnecessary.

You're still the engineer. Understand each decision.

The Missing Nav Link

7 phases implemented, 17 tests passing

But the feature was unreachable.

Nobody asked: "How does the user get here?"

Fix the Process, Not Just the Bug

The /interview skill across 3 iterations

Iteration 2 Missed nav links
→ Added: mandatory topics + user stories

Iteration 3 Dead-end screens
→ Added: state transitions + terminal states

Iteration 4 26 user stories
→ Zero missing entry points

Upgrading /interview

SKILL.md Iteration 2

Interview me relentlessly
about every aspect…

Use AskUserQuestion tool to
ask about anything…

Explore the codebase for
code questions.

Write the spec to a file.

SKILL.md Iteration 4

## Mandatory interview topics

- Entry points
- User journey start-to-finish
- Edge cases
- State transitions
- Terminal states & dead ends

## Output format

Spec MUST end with User Stories.
Navigation stories come FIRST.

Filling the JVM Skill Gap

jvmskills.com

  • Curated AI coding skills for the JVM ecosystem
  • Each skill teaches what Claude doesn't know
  • Expert-authored, community-driven
  • Spring Boot, jOOQ, PostgreSQL, Kotlin, testing

The Rules That Actually Stick

Hooks — deterministic in a non-deterministic world

1
Intro
2
Basics
3
Context
4
Skills
5
Hooks
6
MCP
7
Workflows
8
Tips
9
Outlook

Fire on Every Tool Call

Shell commands at lifecycle events — the ones I use

SessionStart PreToolUse (tool runs) PostToolUse SessionEnd

4 hook types

  • command — shell script
  • http — POST to a URL
  • prompt — single-turn LLM eval
  • agent — multi-turn verification

Hooks I Use

Event Matcher Hook Does
PreToolUse Bash git-guardrails.sh Block dangerous git commands
Bash pre-commit-gate.sh Block commit if lint or compile fails
PostToolUse Edit|Write post-edit-lint.sh Feed lint violations back after every edit
SessionEnd stop-uncommitted-check.sh Warn on uncommitted work

Pre Tool Use — Block Before It Happens

Exit 2 = hard block. Claude cannot proceed.

# git-guardrails.sh
DANGEROUS_PATTERNS=(
  "git push" "git reset --hard"
  "git clean -fd" "git branch -D"
  "git checkout \." "git restore \."
)

for pattern in "${DANGEROUS_PATTERNS[@]}"; do
  if echo "$COMMAND" | grep -qE "$pattern"; then
    echo "BLOCKED: '$COMMAND' matches '$pattern'" >&2
    exit 2
  fi
done

Pre Tool Use — Enforce Conventions

TEST_PATTERNS=(
  "gradlew test " "gradlew test$"
  "gradlew.*--tests"
)

for pattern in "${TEST_PATTERNS[@]}"; do
  if echo "$COMMAND" | grep -qE -- "$pattern"; then
    deny "Use the /test skill instead"
  fi
done
⏺ Bash(git stash && ./gradlew test --tests "*LoginTest")
  ⎿  BLOCKED: Use the /test skill instead of
     running gradlew test directly

Post Tool Use — The Self-Correction Loop

I need to serialize participants to JSON.

⏺ Added 1 line
    3 + import com.fasterxml.jackson.databind.ObjectMapper
# detekt.yml
ForbiddenImport:
  imports:
    - value: 'com.fasterxml.jackson.databind.ObjectMapper'
      reason: 'Use tools.jackson.databind.json.JsonMapper
        instead (Jackson 3)'

That mistake can never happen again.

Anything you can put in a hook — put it in a hook.

Beyond the Terminal

MCP — Model Context Protocol

1
Intro
2
Basics
3
Context
4
Skills
5
Hooks
6
MCP
7
Workflows
8
Tips
9
Outlook

MCP: One Protocol, Every Tool

An open standard for connecting AI tools to external data sources.

CategoryExample
Error monitoringSentry
Issue trackingLinear
DocumentationLibrary docs
CommunicationSlack
IDE integrationJetBrains
Custom internal toolsYour own

What's Plugged In

ServerWhat it gives me
IntelliJReformat, Rename, Run Configuration
LinearIssue tracking
SentryError investigation
JavaDoc CentralUp-to-date JVM library docs
Spring AIBuild your own MCP server

Agent Developer Experience

Without MCP
  • Reformat with sed
  • Fix imports manually
  • Start via Gradle
With IntelliJ MCP
  • reformat_file
  • Auto import optimization
  • execute_run_configuration

The IDE becomes an API that the agent calls.

MCP Steroid — the agent is the user, not the developer

Wiring It Up

CLAUDE.md rule

Use mcp__jetbrains__reformat_file for formatting after editing
      

/restart skill — MCP wrapped in a reliable workflow


1. Stop old app:    restart-app.sh stop
2. Start via MCP:   execute_run_configuration()
3. Wait for ready:  restart-app.sh wait
4. Check errors:    post-bash-log-check.sh
      

From Vibe Coding to
Agentic Engineering

Not every task needs the same workflow.

1
Intro
2
Basics
3
Context
4
Skills
5
Hooks
6
MCP
7
Workflows
8
Tips
9
Outlook

Level 1: Human in the Loop

  • Describe, watch, redirect — real-time collaboration
  • Most interactive mode — you stay in control

Scope grows. Plan before you type.

Level 2: Built-in Plan Mode

  • Claude self-invokes or you trigger with Shift+Tab
  • Explore, prototype, iterate — no upfront spec needed

When Claude keeps guessing wrong.
Requirements only exist in your head.

Level 3: Spec-Driven Development

  • Drive the design before code exists
  • Pair on the spec — with domain experts, UX, product

When the “what” isn’t obvious — only the “why”

Same Teammate, Better Onboarding

Iteration 1 vs Iteration 2

Iteration 1 (no config) Iteration 2 (full setup)
Tests 0 37
Design decisions 0 (Claude guessed) 20 (interview)
CLAUDE.md rules added 0 8 during session
Side-terminal sessions 0 3

Level 4: The Drift Problem

Plans work — until the context window doesn’t.

P1
P2
P3
P4
P5
P6
P7
/frontend-design
Dropped after Phase 1
agent-browser
Dropped after Phase 3
/simplify
Never used

The agent isn’t a deterministic machine

Level 5: The Ralph Loop

One fresh agent per phase. No drift.

Interview Spec Plan Phase 1 Agent Phase 2 Agent ...

"The day shift is thinking,
the night shift is Claude implementing"
— Matt Pocock

From Checklist to Contract

Iteration 5: Flat markdown checklist
- [ ] RED: failing test
- [ ] GREEN: implement

Claude ticks boxes in prose. No enforcement.

39 tests.

Iteration 7: TodoWrite tool
TodoWrite([
  {content: "RED: failing test"},
  {content: "GREEN: implement"}
])

Every task is tracked state.

60 tests.

Instructions become tracked state.

Get a Second Opinion

Same branch, same prompt — two reviewers.

Claude found
  • Timer hangs if beamer tab closes
  • Guest never redirected on finish
  • Race condition in budget check
Codex found
  • Manager controls exposed to guests
  • Script injection in lobby guest list
  • Race condition in budget check

Different models. Different blind spots.

The Tools Behind the Workflow

1
Intro
2
Basics
3
Context
4
Skills
5
Hooks
6
MCP
7
Workflows
8
Tips
9
Outlook

Contain the Blast Radius

/sandbox and Docker Sandboxes

/sandbox

Filesystem + network fenced at the OS level

Docker sandboxes

microVM isolation — disposable, survives a rogue agent

Talk to Claude

handy.computer — speech-to-text

Handy mascot
  • Open source, local, unlimited
  • Voice is faster
  • Extracts the intent
  • Speaking exposes your reasoning

Three Agents, Zero Conflicts

Worktrunk — git worktrees for parallel AI agents

branchnew git branch
worktreeisolated working dir
Postgresdedicated instance
LocalStackdedicated S3 + email
IntelliJopens on this branch
Clauderuns in this shell

Clean History From Messy Sessions

/rebase-commit

46 debugging commits

fix compile, fix again, revert, retry…

10 feature commits

Each with its feature + tests, independently reviewable

Each commit contains a feature and its tests

Your New Job

1
Intro
2
Basics
3
Context
4
Skills
5
Hooks
6
MCP
7
Workflows
8
Tips
9
Outlook

Your Agent Stack

CLAUDE.md

Project context, earned line by line

Every session is day one for the agent. A CLAUDE.md grown from real failures in your repo — build commands, conventions, the “don’t do this” rules — so the next session starts where the last one left off.

skills/

The documentation you should have written

Skills like /interview, /tdd-task, /test, /commit, /restart — tuned to your Spring Boot version, your build system, your test stack. Your senior devs’ knowledge, written down once, applied every session.

hooks/

The rules that actually stick

PreToolUse / PostToolUse hooks block dangerous git commands, gate commits on lint + compile, feed Detekt violations back into the next turn. Constraints compound. That mistake can never happen again.

workflow

From vibe coding to agentic engineering

Five levels: human in the loop → plan mode → spec-driven → the drift problem → Ralph loop. Match the approach to the problem — bug fixes don’t need a 9-phase plan, complex features don’t survive vibe coding.

Discipline, Relocated

Engineering quality doesn't disappear when AI writes code.
It migrates to specs, tests, constraints, and risk management.
— Thoughtworks, Deer Valley 2026

What Only You Can Do

When implementation is cheap,
the bottleneck moves to judgment.

  • What to build
  • Whether it's right
  • Owning the outcome

Building bad software has never been easier.

The speed is free.
The discipline is yours.

You will fall behind.

Not because AI replaces you —
because the engineers using it compound daily.

Start Now or Catch Up Later

curl -fsSL https://claude.ai/install.sh | bash

Observe what goes wrong. Add a rule. Repeat.

Your setup compounds.

Every rule, every skill, every session.

Thank You

Thomas Schilling

Talk resources QR code

Everything from this talk

jvmskills.com/spring-io-2026

OPEN TO WORK

Consulting · Full Time

Spring I/O 2026 · Barcelona