ADRs for small projects: how to document technical decisions without bureaucracy

Practical guide to using Architecture Decision Records in personal projects or small teams without falling into heavyweight documentation.

Cover for ADRs for small projects: how to document technical decisions without bureaucracy

A few months ago I opened a personal project I hadn’t touched in a year. Kotlin with Spring Boot, an API for RSS feed management. I found a decision I didn’t understand: why it used Redis as a feed cache instead of Spring’s in-memory cache. The code worked, the tests passed, but I had no idea why I’d chosen Redis for a project running on a single instance.

It took me an hour to reconstruct the reasoning. I went through commits, commit messages, even an old Slack thread with myself. Eventually I found the reason: I needed the cache to persist between restarts so I wouldn’t re-fetch all feeds on startup. It made sense. But an hour of archaeology for a decision that would have taken 10 lines of documentation.

Since then I use ADRs. Not the enterprise version with 3-page documents and an approval committee. A lightweight version that works for personal projects and small teams. Writing one takes 5 minutes. Not writing one costs hours of future confusion.


What is an ADR (without overcomplicating it)

An Architecture Decision Record is exactly what the name says: a record of an architecture decision. Nothing more, nothing less.

It’s not a design document. It’s not a spec. It’s not a corporate justification. It’s a brief note that answers four questions:

  1. What decision was made.
  2. In what context it was made.
  3. What alternatives were considered.
  4. What consequences it has.

If specs define what will be built, ADRs explain why it was decided to build it one way and not another.

The relationship with SDD is complementary. The spec says “the notification system sends emails asynchronously.” The ADR explains “we chose an event system with Spring Events instead of an external message queue because the expected volume doesn’t justify the operational complexity of maintaining a RabbitMQ.”


Simple ADR template

This is the template I use. Intentionally minimal:

# ADR-001: [Decision title]

**Status**: Accepted | Superseded by ADR-XXX | Deprecated
**Date**: YYYY-MM-DD
**Author**: [name]

## Context

[What problem or situation motivated this decision. 2-4 sentences.]

## Decision

[What was decided. Clear and concrete. 1-3 sentences.]

## Alternatives considered

### [Alternative A]
- Pros: ...
- Cons: ...

### [Alternative B]
- Pros: ...
- Cons: ...

## Consequences

[What this decision implies. Positive and negative. What you gain, what you
lose, what technical debt you take on.]

Five sections. None should exceed a paragraph except the alternatives. If you need more than one page to explain an ADR, you’re probably documenting a design, not a decision.


Real example: why Kotlin instead of Java for a microservice

Let me fill in the template with a real example of a decision we made on a project:

# ADR-003: Use Kotlin instead of Java for the notification service

**Status**: Accepted
**Date**: 2026-03-15
**Author**: Roger

## Context

We're creating a new notification microservice that will integrate with the
existing order system (Java/Spring Boot). The team has experience in both
languages. The service will be relatively small (~15 endpoints, event logic
and email templates) and will be maintained by 2-3 people.

## Decision

Write the notification service in Kotlin with Spring Boot 3.x.
The existing order service stays in Java.

## Alternatives considered

### Java 21 with Spring Boot 3.x
- Pros: consistency with the order service, less learning curve for the
  team, more candidates if we hire.
- Cons: more boilerplate for DTOs and data classes, manual null safety
  (Optional or annotations), no native coroutines.

### Kotlin with Spring Boot 3.x
- Pros: data classes reduce boilerplate in DTOs and events, native null
  safety (important because we handle user data that may come incomplete),
  coroutines for async calls to the email provider, extension functions
  for clean mappers.
- Cons: one team member has less Kotlin experience, some minor
  incompatibility with pure Java libraries.

### Kotlin with Ktor
- Pros: 100% Kotlin stack, lighter than Spring.
- Cons: smaller ecosystem, less tooling, the team has no Ktor experience,
  integration with the rest of our infra (Spring Cloud Config, Actuator)
  would require extra work.

## Consequences

- The team needs to maintain two languages (Java in orders, Kotlin in
  notifications). We accept that complexity because the productivity
  benefit of Kotlin for this specific service compensates for it.
- The team member with less Kotlin experience will have pair programming
  during the first 2 weeks.
- Shared modules (events, common DTOs) are written in Kotlin but are
  interoperable with Java.
- If we migrate the order service to Kotlin in the future, the experience
  gained here will be useful.

This was written in 10 minutes. Six months later, when someone new asks “why is this service in Kotlin and the other one in Java,” the answer is documented. No need to reconstruct the reasoning from scratch.


When to write an ADR (and when you don’t need one)

Not every technical decision needs an ADR. If you documented every architectural if, you’d spend more time writing ADRs than code. The key is identifying decisions that meet at least one of these conditions:

Does need an ADR

  • It’s hard to reverse. Choosing the database, the language, the framework, the module structure. Changing these later costs weeks or months.
  • There were reasonable alternatives. If there were several valid options and you chose one for specific reasons, those reasons should be documented.
  • Someone is going to ask you why. If you can anticipate that a future team member (or your future self a year from now) will look at that code and think “why was it done this way,” that’s a sign it needs an ADR.
  • It has long-term implications. Decisions that affect performance, scalability, operational costs, security, or future maintainability.

Doesn’t need an ADR

  • It’s a standard convention. “We use REST for the API” doesn’t need an ADR if your entire stack is REST. It’s the default.
  • It’s trivially reversible. Choosing between two logging libraries that do the same thing and can be swapped in 5 minutes doesn’t deserve an ADR.
  • There were no alternatives. If a technical requirement forces you to use a specific technology, there’s no decision to document. You can mention it in the spec.
  • It’s already explained in the spec. If the spec explicitly says “we’ll use events for async communication” and explains why, you don’t need an ADR that repeats the same thing.

When in doubt, ask yourself: if I left the project tomorrow and someone new took over, would this decision confuse them. If the answer is yes, write the ADR.


Where to store ADRs

In the repository. Period. Same as skills, ADRs live alongside the code they describe.

project/
├── docs/
│   └── adr/
│       ├── 001-use-postgresql-instead-of-mongodb.md
│       ├── 002-feature-based-structure-not-layer-based.md
│       ├── 003-kotlin-instead-of-java-notification-service.md
│       ├── 004-redis-for-feed-cache.md
│       └── template.md
├── src/
└── ...

The naming convention NNN-descriptive-title.md is simple and works. The number gives chronological order. The title is descriptive so you can find the relevant ADR without opening every file.

Benefits of keeping them in the repo:

  • They’re versioned with git. You can see the evolution of decisions.
  • They go through PRs. A new ADR gets reviewed like any other change.
  • They’re searchable with grep/search. No special tool needed.
  • Anyone who clones the repo has immediate access.
  • They’re next to the code they affect, not in a wiki nobody visits.

For projects with multiple repos, each repo has its own specific ADRs. If there are decisions that affect the entire platform, they can live in a central documentation repo, but that’s for larger teams.


ADRs and AI agents: more useful than you’d think

This is where ADRs fit into the AI workflow I’ve been describing in previous articles.

An AI agent working with your code needs context. Skills tell it how to write code. Specs tell it what to build. And ADRs tell it why things are the way they are.

This is particularly useful when the agent needs to make a decision or when a developer asks it for a recommendation. Without ADRs, the agent proposes whatever it considers best based on its general training. With ADRs, it can consider the specific context of your project.

Example prompt that leverages ADRs:

Project context:
- Skills: backend-conventions.md
- Relevant ADRs:
  - ADR-003: We use Kotlin instead of Java for new services
  - ADR-004: Redis as cache for data that needs to persist between restarts
  - ADR-007: Spring Events for communication between modules in the same service

Task: I need to add a caching system for LLM responses in the summary service.
Responses don't change frequently and are expensive to generate. Propose an
implementation that's consistent with existing architectural decisions.

With the ADRs as context, the agent will propose Redis (consistent with ADR-004) instead of inventing its own solution. It will use Kotlin (consistent with ADR-003). And if the communication is internal to the service, it will use Spring Events (consistent with ADR-007).

ADRs turn the agent from a generic programmer into a programmer who knows the context and decisions of your project.


ADRs for decisions you already made

A common mistake is thinking ADRs are only for future decisions. In reality, the greatest immediate value lies in documenting decisions you already made that currently have no explanation.

If you open your project and there are five things that “work this way but nobody really knows why,” those are your first five ADRs. They don’t need to be perfect. An approximate ADR is better than having nothing.

My process for documenting retroactive decisions:

  1. Identify the decision. Something in the code that’s not obvious. “Why do we use Redis here,” “why is this module separate,” “why is there a cron instead of a trigger.”
  2. Reconstruct the context. Review commits, PRs, Slack messages, your memory. It doesn’t need to be perfect.
  3. Write the ADR with what you know. If you don’t remember the alternatives you considered, write the ones you would have considered. If you don’t remember exactly why, write your best hypothesis and mark it with a note like “[reconstructed, may not be exact].”
  4. Set the status. If the decision is still valid, “Accepted.” If it no longer makes sense but is kept out of inertia, “Deprecated” or “Under review.”
# ADR-004: Redis as RSS feed cache

**Status**: Accepted
**Date**: 2025-04-XX (approximate, reconstructed retrospectively)
**Author**: Roger

## Context

[Reconstructed] The RSS feed service re-fetches all feeds on restart. With
~200 feeds and provider rate limits, a full restart took 15-20 minutes. I
needed a cache that would persist between restarts.

## Decision

Use Redis as the cache for parsed feeds. 1-hour TTL per feed.

## Alternatives considered

### In-memory cache (Spring Cache + ConcurrentHashMap)
- Pros: zero additional infrastructure.
- Cons: lost on restart. Restart = massive re-fetch.

### SQLite/H2 as disk cache
- Pros: persists without an external service.
- Cons: more complex to implement, not designed for native TTL.

### Redis
- Pros: native TTL, persists between restarts, atomic operations, low
  overhead.
- Cons: one more service to maintain (Redis).

## Consequences

- Redis adds an infrastructure component to the deploy (docker-compose).
- The service starts in seconds instead of minutes.
- If Redis is unavailable, the service works but without cache
  (degrades to re-fetch).

Five minutes of writing that save an hour of archaeology.


Full workflow: specs, ADRs, and skills together

A project’s documentation ecosystem with SDD consists of three pieces that complement each other:

DocumentAnswersExample
SpecWhat to build and with what criteria”Email notification system with retries and user preferences”
ADRWhy it’s built this way”We chose Kotlin for null safety and coroutines for this service”
SkillHow it’s built on this team”We use constructor injection, tests with MockK, feature-based structure”

None replaces the others. All three together give a developer (or an AI agent) all the context they need to contribute to the project in alignment with the team’s decisions and conventions.


Common mistakes with ADRs

ADRs that are really design documents

If your ADR has sequence diagrams, detailed pseudocode, and 500-word sections, it’s not an ADR. It’s a design document in disguise. The ADR only captures the decision and its reasoning. Detailed design goes in the spec or in separate technical documentation.

ADRs that don’t mention alternatives

If you only write what you decided but not what alternatives existed, you lose the most valuable part. The context of “why not X” is often more useful than “why yes Y.” When someone new proposes switching to X, they can directly see why it was ruled out.

ADRs that never get updated

If a decision no longer applies because the context changed, the ADR should reflect that. Don’t delete it: mark the status as “Superseded by ADR-XXX” and link to the new one. That way you maintain the decision history.

Not writing ADRs because “it’s a small project”

Small projects are precisely where you need ADRs the most. In a 20-person team with formal processes, decisions get discussed in meetings and recorded in minutes. In a personal project or one with 2-3 people, decisions are made in someone’s head and forgotten in two months.


Start today

If you’ve been reading this article and thinking “this makes sense but I don’t know where to start,” here’s the minimal version:

  1. Create a docs/adr/ folder in your project.
  2. Copy the template I showed above into docs/adr/template.md.
  3. Think of one decision in your project that isn’t obvious. The first one that comes to mind.
  4. Write the ADR. Don’t aim for perfection. 10 minutes maximum.
  5. Commit it.

That’s it. You now have your first ADR. You’ll write the next one when you make the next architectural decision or when someone asks “why was it done this way” and you realize the answer should be written down.

It’s not bureaucracy. It’s project memory. And memory, unlike opinions, doesn’t change over time.

OshyTech

Backend and data engineering focused on scalable systems, automation, and AI.

Navigation

Copyright 2026 OshyTech. All Rights Reserved