In Part 1, I showed you what Claude Code is and what it can do. You saw an AI agent that reads your project, writes code, runs your build, and fixes its own mistakes. Impressive stuff.
But here’s the thing. If you just fire up Claude Code and start prompting, the output you get is… generic. It’s correct, sure. But it doesn’t know your team uses MapStruct instead of manual mapping. It doesn’t know your POST endpoints need authentication. It doesn’t know you measure everything with Micrometer and that a controller without metrics is considered incomplete.
Claude Code is like a brilliant new hire on their first day. They’re smart, motivated, and ready to go. But they don’t know the house rules yet.
That’s where CLAUDE.md comes in.
What Is CLAUDE.md?
CLAUDE.md is a plain markdown file you drop in the root of your project (or in a few other strategic locations — more on that shortly). Claude Code reads it at the start of every session, before it looks at a single line of your code.
Think of it as a briefing document. It tells Claude Code:
- How to build your project
- What conventions your team follows
- What patterns to use (and which to avoid)
- What “done” looks like in your codebase
If you read Part 1, you already saw a basic example. But that was just the appetiser. In this article, we’re going to turn CLAUDE.md into a precision instrument.
Why Should You Care?
Here’s a question I get a lot: “Can’t Claude Code just figure this out from looking at the code?”
Sometimes, yes. If your project has 200 files all following the same pattern, Claude Code will pick up on that. But there are things it can’t infer:
- That your team decided last sprint to switch from
@Autowiredfield injection to constructor injection - That every REST endpoint must expose Micrometer metrics
- That POST and PUT endpoints require JWT authentication, but GET endpoints don’t
- That your DTO naming convention is
XxxResponsefor outbound andXxxRequestfor inbound
These are decisions. They live in your team’s collective memory, in your wiki, in your code review comments. Claude Code doesn’t have access to any of that — unless you write it down in CLAUDE.md.
The Scope Hierarchy: Where CLAUDE.md Files Live
This is the part most people get wrong on their first try. CLAUDE.md isn’t a single file — it’s a hierarchy. Claude Code loads them in order, from broadest to most specific:
| Scope | Location | Who it’s for |
|---|---|---|
| Managed policy | /Library/Application Support/ClaudeCode/CLAUDE.md (macOS) | Your entire organisation — managed by IT/DevOps |
| User (global) | ~/.claude/CLAUDE.md | You, across all your projects |
| Project | ./CLAUDE.md or ./.claude/CLAUDE.md | Your team, committed to version control |
| Local | ./CLAUDE.local.md | You, for this project only — gitignored |
Claude Code walks up the directory tree from your working directory, picking up every CLAUDE.md and CLAUDE.local.md it finds. It concatenates all of them into one block of context, with the most specific files loaded last.
Within each directory, CLAUDE.local.md is appended after CLAUDE.md, so your personal notes are the last thing Claude reads at that level.
For Java developers, this hierarchy maps nicely to how we already think about configuration. It’s like Spring’s property resolution:
- Managed policy = your
application.ymlbaked into the company base image - User global = your
~/.m2/settings.xml - Project =
application.ymlinsrc/main/resources - Local =
application-local.yml— your overrides, not committed
The mental model is the same: more specific wins.
What Goes Where?
Here’s how I split things in practice:
~/.claude/CLAUDE.md (global, personal): things that follow you across projects.
| |
./CLAUDE.md (project, shared): things your team agrees on. This is the one you commit.
| |
./CLAUDE.local.md (local, personal): things only you need.
| |
The Token Tax: Why Size Matters
Here’s something that catches people off guard.
Your CLAUDE.md is loaded into the context window at the start of every single session. It sits there, consuming tokens, before you’ve even typed your first prompt. And it’s not just at the start — it persists across turns.
Let me put some numbers on that. Say your CLAUDE.md is 3,000 tokens. That’s a decent-sized file — maybe 150 lines of markdown. Over a 40-turn session, that’s 120,000 tokens of “tax” you’re paying just for the briefing document.
Now multiply that by however many sessions you run in a day.
The official recommendation from Anthropic is to keep each CLAUDE.md file under 200 lines. I’d go further and say: treat your CLAUDE.md like a tweet thread, not a wiki article. Every line should earn its place.
Here are some practical tips:
Be terse. You’re writing for an LLM, not for a new hire’s onboarding document.
| |
Use HTML comments for human-only notes. Block-level HTML comments are stripped before injection into Claude’s context. They cost you zero tokens.
| |
Offload details to path-scoped rules. If a rule only matters for your API layer, don’t burn tokens loading it when Claude is editing a database migration. We’ll cover this later in the article.
Progressive Example: From Generic to Production-Quality
This is where it gets fun. Let’s use the same scenario from Part 1 — adding a Conference entity and REST endpoint — and watch how the output improves as we refine our CLAUDE.md.
Round 1: No CLAUDE.md — The Blank Slate
You fire up Claude Code in a fresh Spring Boot project. No CLAUDE.md, no rules, nothing. You type:
| |
Claude Code produces something like this:
| |
| |
| |
It works. But look at what’s wrong from a “house rules” perspective:
- Field injection with
@Autowired— your team banned this last quarter - Returns entities directly — no DTO layer, no MapStruct
- No metrics — production endpoints need Micrometer counters
- No authentication — this endpoint should be open, but there’s no security configuration at all
Longfor the ID — your team usesUUID- No DTOs at all — just raw entities over the wire, no request/response separation
- No tests — not a single test class
It’s the “brilliant new hire” problem. Technically correct, organisationally wrong.
Round 2: Basic CLAUDE.md — Teaching the Conventions
Now let’s add a CLAUDE.md with your team’s core conventions:
| |
Same prompt:
| |
Now the output looks like this:
| |
| |
| |
| |
| |
Much better. Constructor injection, a separate DTO layer, MapStruct mapper, UUID primary keys, proper package structure. Claude Code also generated a ConferenceControllerTest with @WebMvcTest and a ConferenceServiceTest with Mockito.
Notice, though, that the DTO is a plain class with getters and setters — we never told Claude to use records. It’s following our naming convention (XxxResponse), but it defaulted to a traditional POJO. We’ll fix that in Round 3.
But we’re not done yet.
Round 3: The Full Briefing — Metrics, Security, and Production Standards
Now let’s add the rules that make the difference between “it compiles” and “it passes code review”:
| |
Same prompt again:
| |
Now Claude Code produces a full slice. Here are the highlights of what changed:
The controller now has metrics and security baked in:
| |
The request DTO has Bean Validation
| |
The DTOs are now records — remember the plain class from Round 2? Gone.
| |
There’s a global exception handler:
| |
And the security configuration is there:
| |
The test suite now includes security assertions:
| |
That’s the progression. Same prompt, three completely different levels of output. The code in Round 3 would actually pass a code review. The code in Round 1 would get sent back with a dozen comments.
The CLAUDE.md didn’t change the prompt. It changed the baseline.
Now that you’ve seen the impact, let’s look at some techniques to keep your CLAUDE.md maintainable as it grows.
The @import Trick: Keeping CLAUDE.md Lean
Your CLAUDE.md supports an @path/to/file import syntax. This is incredibly useful for referencing existing project files without copy-pasting their content.
| |
Imported files are expanded at launch and loaded into context alongside the CLAUDE.md that references them. Both relative and absolute paths work. You can even nest imports up to five levels deep.
A word of caution, though: imports still consume tokens. If your pom.xml is 500 lines, that’s 500 lines loaded into every session. Use this for files Claude genuinely needs to reference, not as a “let me dump everything” mechanism.
A pattern I find useful: reference your pom.xml so Claude knows exactly what dependencies are available, and reference a short architecture doc so it understands the high-level design. But don’t import your entire docs/ folder.
| |
For personal overrides that shouldn’t be committed, use CLAUDE.local.md and import from your home directory:
| |
This way, your personal rules follow you across worktrees of the same repo.
Path-Scoped Rules: Load Only What’s Needed
As your CLAUDE.md grows, you’ll start noticing that some rules only matter for specific parts of the codebase. Your API validation rules are irrelevant when Claude is editing a Flyway migration. Your testing conventions don’t apply to Docker configuration.
This is where .claude/rules/ comes in. You can create markdown files in this directory, and optionally scope them to specific file patterns using YAML frontmatter:
| |
Here’s what api-design.md might look like:
| |
And testing.md:
| |
These rules only load when Claude reads files matching the glob pattern. So when you’re working on a controller, Claude sees the API design rules. When you’re writing tests, it sees the testing rules. When you’re editing docker-compose.yml, neither of these loads, saving you tokens and keeping Claude’s context focused.
This is the equivalent of conditional @Profile beans in Spring — configuration that activates only when it’s relevant.
CLAUDE.md vs Auto Memory: Who Writes What?
Claude Code has two memory systems, and it’s worth understanding the difference.
CLAUDE.md is what you write. It’s your rules, your conventions, your instructions. You control every word.
Auto memory is what Claude writes for itself. When you correct it — “No, we use BigDecimal for monetary values, not double” — Claude can save that lesson to ~/.claude/projects/<project>/memory/MEMORY.md. Next session, it remembers.
Think of it this way:
CLAUDE.md= the employee handbook you hand to the new hire- Auto memory = the notes the new hire scribbles in their notebook after their first week
Both are loaded at session start. Your project-root CLAUDE.md survives compaction (when Claude trims its context window) — it gets re-read from disk and re-injected. Auto memory lives on disk too, so it’s always available at the start of the next session. They serve different purposes.
Use CLAUDE.md for:
- Architectural decisions (constructor injection, MapStruct, layered architecture)
- Build and test commands
- Naming conventions
- Security requirements
- Non-negotiable standards
Let auto memory handle:
- Build quirks Claude discovers (“running
mvn teston this project requires Docker to be running”) - Debugging patterns (“the Flyway migration error usually means the local DB is out of sync”)
- Your personal workflow habits (“Ron prefers to see the plan before any code is written”)
You can check what Claude has memorised by running /memory in a session. Everything is plain markdown you can read, edit, or delete. If Claude learned something wrong, just fix the file.
Putting It All Together: My Recommended Setup
Here’s the setup I use across my Spring Boot projects. It’s opinionated, but it works.
| |
The main CLAUDE.md stays under 100 lines: build commands, project architecture, and the most critical conventions. Everything else goes into path-scoped rules that only load when relevant.
Tips From the Trenches
After months of using CLAUDE.md in real projects, here are the things that made the biggest difference:
Start small. Don’t try to write the perfect CLAUDE.md on day one. Start with your build command and three conventions. Add rules as you find yourself correcting Claude.
Be specific, not vague. “Use 2-space indentation” beats “format code properly.” “Every controller method must have a @Timed annotation” beats “add observability.” If you can’t verify it, Claude can’t follow it reliably.
Use /init to bootstrap. Run /init in your project and Claude will analyse your codebase and generate a starting CLAUDE.md. It won’t be perfect, but it’s a solid starting point. You can set CLAUDE_CODE_NEW_INIT=1 for an interactive flow that asks follow-up questions.
Review it like code. Your CLAUDE.md lives in version control. Treat changes to it with the same scrutiny you’d give a pull request. A bad rule in CLAUDE.md can silently degrade every piece of code Claude generates for your team.
Watch for contradictions. If your project CLAUDE.md says “use records for DTOs” but a subdirectory CLAUDE.md says “use classes with Lombok for DTOs,” Claude might pick either one. Periodically review your files and remove outdated rules.
Use HTML comments as changelogs. Since block-level HTML comments are stripped from the context, you can leave notes for your team without any token cost:
| |
What’s Next?
You now have the tools to make Claude Code behave less like a generic code generator and more like a team member who actually read the wiki. A well-crafted CLAUDE.md is the single highest-leverage thing you can do to improve your Claude Code experience.
But conventions are just the start. In Part 3, we’ll look at Skills — the SKILL.md architecture that lets you teach Claude Code how to perform complex, multi-step tasks like generating a full REST slice, running a code review, or scaffolding a new microservice. Think of skills as the difference between telling someone the rules and showing them how it’s done.
See you there.
This is part 2 of a 10 parts series.
Previous: Part 1 — Introduction: What Is Claude Code?
Next: Part 3 — Skills: Teaching Claude Code Your Standards — The SKILL.md architecture, auto-triggered vs explicit skills, writing your own, and best practices for skill design.
