From Spec-Driven Development to Living Specifications in Java Projects

Author: Maximillian Arruda

Original post on Foojay: Read More

From Spec-Driven Development to Living Specifications in Java Projects

Coding agents have changed the speed at which we can produce software. But producing code faster does not automatically mean producing better software.

As I started using agents more frequently in real-world software development, one question became increasingly important: how do we give an agent enough freedom to be useful without losing engineering intent?

Trying to control every line of code generated by an agent removes much of the value it can provide. At that point, it might simply be faster to write the code ourselves.

On the other hand, merely describing an outcome and accepting any implementation that produces it brings us dangerously close to vibe coding.

There must be something between those two extremes.

Specifications seem to offer a promising answer. They can make intent explicit before implementation, establish boundaries for the agent, and provide something against which the resulting software can be evaluated.

This is what we call Spec-Driven Development.

But after experimenting with this approach, another question started to feel more interesting than simply how to use a specification to drive a change:

What happens to the specification after the change is complete?


The Promise of Spec-Driven Development

One of the approaches that strongly influenced this journey came from my dear fellow Oracle ACE and Java Champion Loiane Groner, through her article “Vibe Coding, But Production-Ready: A Specs-Driven Feedback Loop for AI-Assisted Development.”

What caught my attention was the idea of bringing engineering discipline to AI-assisted development without giving up the productivity that coding agents could provide.

The cycle progressed incrementally from product intent to implementation and verification:

Instead of giving the agent an isolated prompt and immediately asking for code, each step produced context for the next one.

Talking with a friend from the community and fellow open-source contributor, Matheus Oliveira, we decided to put these ideas into practice.

We materialized this cycle as a set of Agent Skills that we called SLDD.

And the first important discovery was simply seeing a workflow like this operate from beginning to end.

The workflow gave us structure. Assumptions could be questioned before implementation, technical decisions could be reviewed, and tests became part of the path between intent and code rather than something added afterward.

But experimenting with an idea does more than tell us whether it works.

Experimentation exposes situations we had not anticipated and, more importantly, allows us to ask new questions. That is exactly what happened with SLDD.

For that, I am particularly grateful to Loiane.

Her article did more than introduce us to an interesting approach. It encouraged us to experiment with it. Some of the questions that eventually shaped this journey only became visible because we first had the opportunity to put those ideas into practice.

Thank you, Loiane, for sharing your ideas and inspiring us to experiment with them.


What We Learned by Putting It into Practice

As we continued experimenting with SLDD and exploring other Spec-Driven Development approaches, including OpenSpec and GitHub Spec Kit, we started paying less attention to the individual steps of each workflow and more attention to the model behind them.

Specifications can be extremely useful while a change is happening.

They make intent explicit, preserve decisions, provide context to coding agents, and create checkpoints before an implementation moves too far in the wrong direction.

But those benefits also come with trade-offs.

Multiple specification and design artifacts mean more context to produce, persist, retrieve, and interpret. More workflow steps can also mean more human transitions: generate, review, approve, continue.

At some point, we noticed something uncomfortable in our own experiment:

the human was becoming part of the workflow’s state machine — in other words, becoming the bottleneck of the process.

Instead of participating only when engineering judgment was necessary, sometimes we were simply allowing the process to move from one state to another.

But an even more important question appeared after the workflow was complete.

Imagine that a specification successfully drives a feature all the way to production.

A few weeks later, someone fixes a small bug directly in the implementation.

The tests pass, the code is reviewed, and the change is deployed.

But the original specification is never updated.

The software has evolved while its specification continues to describe a previous reality.

The lifecycle of a change is much shorter than that of software, which tends to remain in constant evolution.

This shows that a specification can be excellent at driving a change and still lose its usefulness after that change becomes part of the system.

And that raises another interesting question:

How can a specification remain useful after the change becomes part of the software?


When the Specification Is Not the Source of Truth

Spec-Driven Development is often presented around the idea that the specification becomes the source of truth.

As an expression of software intent, that can be an interesting idea, but there is a slightly uncomfortable practical detail:

Specifications do not run in production. Code does.

That does not make the specification useless. It simply means that it represents a different kind of truth:

  • The specification describes the intended behavior.
  • The code implements the actual behavior.
  • Tests provide executable evidence connecting the two.

This leads us to a model that became increasingly important:

And yes, the arrows matter, because software evolves in both directions:

  • Sometimes, the implementation accidentally violates an existing requirement, and the code needs to converge toward the specification.
  • At other times, the implementation evolves intentionally, and the specification needs to follow that change.

This model allows the specification to become what we can call a Living Specification.

That does not mean it will update itself “magically.” Drift can still happen. The idea has a much more pragmatic goal:

make divergence visible and convergence cheap.

This changed what we were looking for.

We wanted specifications close enough to the implementation that developers and agents would naturally find them.

We wanted requirements precise enough to be testable.

And we wanted the specification to describe the current promise of a software component, rather than merely preserve the history of the change that created it.

That was when I found SBCE.


SBCE: Bringing the Specification Closer to the Code

SBCE, pronounced “space”, is a Spec-Driven Development workflow created by Java Champion Adam Bien, built around the Boundary-Control-Entity (BCE) architectural style.

One of its ideas immediately connected with the questions we had been asking:

the specification lives alongside the code.

In SBCE, each Business Component (BC) keeps its specification inside package-info.java, as Markdown Javadoc (Java 23+), located within its own package.

This removes the need to maintain a parallel tree of specifications in a separate structure far away from the code.

Of course, this does not magically make documentation executable or eliminate drift, but it changes where the specification lives.

The specification becomes part of the same structural neighborhood as the code, making the relevant pieces easier for both developers and coding agents to find when working on that component.

Another important idea was expressing the behavior of a Business Component through testable and traceable requirements, using structures inspired by EARS.

In SBCE, these requirements are expressed in English.

Instead of an ambiguous requirement such as:

The checkout should work correctly.

we can express something observable:

When a checkout is requested for an empty cart, the Business Component shall reject the request.

By assigning a stable identifier to that requirement, we can establish a relationship such as:

The agent still has the freedom to reason about the implementation, but the expected behavior now has a deterministic verification boundary.

SBCE also reinforced another lesson for us:

simplicity matters.

Its main workflow is deliberately small, providing two modes:

  • /sbce new
    • declares a Business Component, writing its specification into the package’s package-info.java;
  • /sbce apply
    • works toward converging detected drift between specification and code.

Other concerns, such as coding conventions, can be delegated to composable skills rather than continuously expanding a huge set of instructions.

While exploring SBCE, I had the opportunity to talk with Adam about bringing this idea to Java projects using stacks other than MicroProfile, such as Spring Boot.

The conversation was very interesting and clarified an important distinction about SBCE:

SBCE is opinionated about architecture, but extensible regarding technology.

BCE provides the architectural foundation and the meaning of a Business Component within SBCE.

Adam described how additional skills could be composed to work with other stacks, using the concept of Inversion of Control within the process.

This meant that supporting Spring Boot did not necessarily require changing SBCE. A Spring Boot-specific skill could map BCE to that stack.

But this also revealed what our actual problem was.

Most Java projects we work with are brownfield. Moreover, many of these applications use Spring Boot and do not adopt BCE as their primary architecture.

Some use package by feature, others use package by layer, and in many cases we find a mixture of architectural decisions accumulated over the years.

To bring these ideas to those projects, we would need to do more than change the technology stack: we would also need to deal with architectures beyond BCE.

And that created a new opportunity for experimentation.


SDD4J: Adapting to Multiple Architectures in Java Projects

The question we wanted to explore was relatively simple:

What if the meaning of a Business Component could adapt to the architecture of the Java project?

Instead of making the main specification workflow understand every possible architecture, we introduced the idea of architecture adapters.

Conceptually:

The core remains concerned with specifications, testable requirements, traceability, and convergence.

The adapter answers an architectural question:

Where is the Business Component in this project?

In SDD4J, a Business Component represents a business capability. What changes across architecture adapters is how that capability is located and bounded within the project’s existing structure.

  • In BCE, the BCE structure already provides that boundary.
  • In package by feature, the feature package itself can naturally provide that boundary.
  • In package by layer, a business capability may be distributed across multiple technical packages, requiring a different mapping strategy.

This is particularly important in brownfield development.

We did not want adopting Living Specifications to require reorganizing an existing application first.

Therefore, the workflow should recognize the project as it already is.

Based on this model, we created the SDD4J workflow. It provides a small set of operations:

  • /sdd4j setup establishes the project context, such as its architecture and the language used for specifications.
  • /sdd4j new creates the specification for a new Business Component and its testable requirements without modifying the code.
  • /sdd4j apply looks for divergence between the specification, tests, and implementation and resolves the detected drift to bring them back into alignment.
  • /sdd4j verify looks for executable evidence that the declared requirements remain represented by tests and software behavior.

The language configured during setup is also used when writing EARS requirements.

This allows a project configured for PT-BR, for example, to keep its specifications and requirements in that language.

The same requirement presented earlier could therefore be expressed in an SDD4J project configured for PT-BR as:

Quando um checkout for solicitado para um carrinho vazio, o Business Component deverá rejeitar a solicitação.

We preserve the same intent:

but in the language configured for the project.

These operations were also not designed as a mandatory state machine through which every code change must pass.

A small bug fix may not justify an elaborate agent workflow.

Sometimes, the developer should simply make the change.

Sometimes, the agent should implement it.

In other cases, the most useful role for the agent may be to inspect the result afterward and identify missing tests or specification drift.

The goal is not to create ceremony.

The goal is to preserve enough structure so that humans and agents can understand what a Business Component promises to do and verify whether the software continues to fulfill that promise.

Other Agent Skills can be composed around this core to provide project-specific engineering guardrails.

The idea is not to teach the agent everything it already knows about Java, Spring, testing, or software design.

It is to provide the delta: the decisions and constraints that specifically matter to that project.

Give the agent enough freedom to reason.

Give it enough constraints to remain aligned with the project.

And keep changes small enough that a human engineer can still understand and review them.


There Is No Silver Bullet

It would be easy to tell this story as a progression: we experimented with one approach, found limitations, discovered another, improved upon that idea, and eventually arrived at SDD4J.

That would be the wrong conclusion.

We did not eliminate the trade-offs.

We chose different trade-offs.

SBCE deliberately anchors itself in BCE while remaining stack-neutral, which means it can be used with Java stacks as well as other technology stacks, such as Web Components.

That gives it a clear definition of a Business Component while allowing technology-specific concerns to vary through skills.

SDD4J deliberately anchors itself in the Java ecosystem while supporting multiple architectural styles.

That gives us native mechanisms such as package-info.java and Javadoc, while allowing the architectural interpretation of a Business Component to vary through adapters.

Neither is universally better.

For a project already organized around BCE, regardless of the technology stack, SBCE may be the simpler and more natural choice.

For an existing Java project organized around a different architecture, SDD4J may be an interesting choice.

In the end, these are different trade-offs.

And other SDD workflows solve other parts of the problem by choosing different constraints.

That is software engineering.

Flexibility is not automatically better than constraint.

More automation is not automatically better than human judgment.

More context does not automatically mean better context.

And a specification does not automatically become true simply because we decide to call it the source of truth.

We started this journey by asking how specifications could better drive software development with coding agents.

Experimenting with that question led us somewhere slightly different.

Today, the question I find more interesting is:

How can we keep software intent understandable, verifiable, and close to the behavior it describes while the software continues to evolve?

That is what we mean by a Living Specification.

SDD4J is our current experiment in that direction, and we invite you to try it out with us.

The workflow is available in the soujava/agent-skills repository, and you can use it in any Java project.

It is open-source and free to use, and we welcome contributions :smile:.

I believe it will continue to evolve. Some of its ideas may prove useful, while others may not. But above all, I hope it helps us continue exploring these questions.

In a world where coding agents and models are constantly changing how we build software, SDD4J itself may eventually become obsolete — and that is perfectly fine. Workflows should evolve as our tools, models, and engineering practices evolve.

Workflows may be temporary, but the software’s intent must outlast any workflow.


What Do You Think?

This article represents where our experiments have brought us so far: not a definitive answer.

And that is precisely why I would like to hear about your experience.

How are you using specifications with coding agents?

Do you treat the specification as the source of truth, or have you also encountered drift between specifications and the code actually running in production?

Have you experimented with Spec-Driven Development, SBCE, OpenSpec, Spec Kit, or a completely different approach?

How about trying SDD4J in an existing Java project? It might be useful to you, or perhaps not. Either way, I hope it inspires new ideas.

And perhaps the question that interests me the most:

What does a Living Specification mean to you?

Share your experiences, disagreements, experiments, and lessons learned in the comments. Those different perspectives are precisely what make this discussion valuable.

And if this article gave you something useful to think about, share it with another developer or team experimenting with AI-assisted software development.

Perhaps the next interesting idea will start from that conversation.

The post From Spec-Driven Development to Living Specifications in Java Projects appeared first on foojay.