Spring Boot · Apache Wicket · Spring Modulith · MongoDB · AI agents

Ship whole features with a team of three.

Most business software doesn't need a frontend team, a backend team and an API between them. With plain Java from the database to the button, two or three people can own every feature end to end, and AI agents take the routine work off their hands. fullstack4j is how allwerker, a Munich cooperative of senior engineers, sets up, staffs and coaches such teams.

One language, one deployable Architecture checked on every build Open source, Apache 2.0 Backed by 19 senior engineers at allwerker
Slices, not layers Three horizontal technical layers (UI, logic, data) are crossed by three vertical feature slices called ordering, billing and shipping. Each slice has one owner and contains its own page, service and repository. Slices talk to each other mostly through events. UI Logic Data Page Service Repo Page Service Repo Page Service Repo ordering billing shipping slices prefer events · boundaries tested
One feature, one slice, one owner, from database to button.
The hidden cost

Your stack quietly decided your team size.

Choose a JavaScript single-page app, a REST API and a Java backend, and you have also chosen an org chart: frontend developers, backend developers, two build pipelines, and an API contract to negotiate for every feature. Each piece is reasonable on its own. Together they turn every user story into a relay race.

Split by technology

The usual setup

  • One feature becomes tickets in two or three backlogs, done by different people at different times.
  • Two languages, two build chains, two dependency treadmills to keep up with.
  • An API between your own UI and your own backend that no user ever asked for.
  • Work waits on whoever is the specialist for the layer that's next.
Split by feature

The fullstack4j setup

  • One feature is one slice, built and shipped by one person or pair.
  • Java and HTML templates. One build, one deployable, one set of dependencies.
  • The UI calls the service directly. Boundaries sit between features, where they matter.
  • Anyone on the team can pick up the next most valuable thing.
36
paths between 9 people
3
paths between 3 people

paths = n(n−1) / 2

Every line is a meeting, a handoff or a misunderstanding waiting to happen. Triple the team and you get twelve times the coordination. Adding people to a split stack mostly buys you more lines.

Removing the layers that force the split is the cheapest way to make a team smaller and faster.

The approach

Small teams. Whole features. Plain Java.

Nothing here is new or exotic. It is mature, well-documented technology, arranged so that a small team can understand the whole product and change any part of it safely.

01

Slices, not layers

Each feature lives in one module: its Wicket page, its use cases, its MongoDB documents and repository, and the events it publishes. One person or pair takes it from idea to production. There are no "frontend part" and "backend part" tickets.

02

One language, one deployable

Server-rendered UI with Apache Wicket: Java components and plain HTML templates, Ajax built in. No JavaScript build chain, no client state to sync, no API contract with yourself. One Spring Boot jar to test, ship and run.

03

Boundaries are tested, not documented

Spring Modulith turns each slice into a module that shows the others only its base package: its page, its events and, where an event won't do, an interface that returns plain records. The fullstack4j rules, built on ArchUnit, add the house rules: what a slice may show, where its documents live, which slice sends which event. When code reaches into another slice's internals, the build fails. Architecture stays intact while people, and agents, write code quickly.

04

AI as the fourth team member

A uniform, typed, well-tested codebase with enforced boundaries is where coding agents do their best work. People decide what to build and write it down in the slice itself; agents build until its tests pass; people review the result.

Anatomy of a slice

What a feature looks like when one person owns it.

Everything a feature needs, in one package you can read in ten minutes. This is ordinary Wicket, Spring Boot and Spring Data MongoDB. The only rule is where things go.

src/main/java/com/acme/shop
shop/
├── ShopApplication.java
├── StartPage.java        links to each slice's page
├── package-info.java     the map: slices, flow, words
├── ordering/             one slice, one module
│   ├── package-info.java         the spec: what it promises
│   ├── OrdersPage.java           UI, plain Wicket
│   ├── OrderShipped.java         event for other slices
│   └── internal/                 hidden from other slices
│       ├── Orders.java           use cases
│       ├── Order.java            one MongoDB document
│       └── OrderRepository.java  MongoRepository
├── billing/              listens to OrderShipped
└── shipping/

src/main/resources/…/shop/
├── StartPage.html
└── ordering/
    └── OrdersPage.html   markup, no JS build

src/test/java/…/shop/
├── ArchitectureTests.java      Modulith + fullstack4j rules
├── SpecTraceabilityTests.java  spec and tests in step
├── TestShopApplication.java    dev run on MongoDB
└── ordering/
    └── OrdersTests.java        one test per requirement
Readable by anyone, starting with the spec. No frontend folder and backend folder to keep in sync. The slice's package-info.java says what it promises in one screen, and it is the first thing a new colleague or an agent reads.
Small enough for one head, and for one agent's context window.
Guarded by tests. billing can react to an order being shipped because OrderShipped sits in ordering's base package; everything in internal stays hidden from it. And every requirement in the spec has a test that carries its id.
Events first, calls when needed. Slices react to each other's events wherever they can. When one really has to ask another something, it calls an interface in that slice's base package, which answers with records defined right next to it; the implementation and the documents stay in internal.
Stored as documents. An order and its lines are one MongoDB document, shaped like the code, so the schema grows with the slice instead of through migrations.
/// # Ordering
/// > Take a customer's order and see it through until it ships.
///
/// ## Operations
/// - `place-order`: take an order for a customer (`Orders.place`)
/// - `open-orders`: list what still has to ship (`Orders.open`)
/// - `ship-order`: hand an open order over to shipping (`Orders.ship`)
///
/// ## Requirements
/// ### R1: Place an order
/// - R1.1 — When a customer places an order with at least one line,
///   the slice shall list it as open.
/// - R1.2 — If an order has no lines, then the slice shall reject it.
///
/// ### R2: Ship an order
/// - R2.1 — When an open order is shipped, the slice shall no longer
///   list it as open.
/// - R2.2 — When an open order is shipped, the slice shall publish
///   `OrderShipped`.
/// - R2.3 — If the order has already shipped, then the slice shall
///   reject the request.
///
/// ## Events
/// - publishes `OrderShipped` (R2.2)
/// - listens to nothing
///
/// ## Entities
/// - `Order`: one document with its customer, lines and status
///
/// ## Out of scope
/// - prices and invoices (`billing`)
/// - parcels and carriers (`shipping`)
@ApplicationModule(displayName = "Ordering", allowedDependencies = {})
package com.acme.shop.ordering;

import org.springframework.modulith.ApplicationModule;
Built for working with agents

Boring technology is AI-friendly technology.

Coding agents are only as good as the feedback they get. A single typed language, fast tests, architecture rules that fail loudly and a short spec in every slice give them exactly that, so a team of three can review more than it has to type.

You: shape the next piece→ Agent: specs it with you, then builds→ Build: compiles, tests, checks boundaries and spec→ You: review and ship

Guardrails, not guesswork

Modulith and ArchUnit tests catch an agent that wanders into the wrong module, or switches off a failing test, before a human has to.

One language in context

No TypeScript types drifting from Java DTOs. The agent sees the page, the service and the data in one place.

Feedback in seconds

The compiler, JUnit, Mockito and WicketTester check UI and logic without a browser, and Testcontainers gives every test run a real MongoDB.

The spec lives in the slice

Each slice carries its contract in its own package-info.java: operations, numbered requirements, events, out of scope. Every requirement has a test with its id, and the build fails when the two drift apart. One screen an agent can re-read on every loop. Based on SBCE by Adam Bien.

SBCE (spec-driven Boundary-Control-Entity) was created by Adam Bien and is described at sbce.space. We use its spec: the package's own doc, with numbered requirements that each have a test. Inside a slice we keep Spring Modulith's default instead of boundary/control/entity: the base package is what other slices and the start page may use, everything else goes in internal.

Beyond the code

The organisational dividend.

Fewer moving parts in the stack means fewer moving parts in the organisation. That is where most of the savings come from, and they are easy to overlook.

Conway's law, on purpose

Teams build systems shaped like their communication. Shape the team around features and the system follows the product, not the tech.

No waiting on specialists

No queue for "the frontend person". Whoever is free takes the next most valuable slice, so work keeps flowing.

One hiring profile

Java and Spring developers are easy to find and slow to burn out on churn. You hire for one skill set and grow it.

Talk about users, not APIs

With little coordination to manage and an appetite instead of estimates, conversations turn to outcomes and value, not story points.

Onboarding in days

A new colleague reads one slice, ships a change to it, and already understands how every other slice works.

Calm dependencies

Mature, slow-moving libraries mean fewer forced migrations, so maintenance doesn't crowd out the roadmap.

For engineering leaders

A team of eight to twelve for a workflow-heavy business application, and it still feels slow.

The same scope with two or three people, one skill set and far fewer moving parts to run.

For founders and product people

Coordination eats the roadmap. Every idea needs a meeting before it needs code.

From idea to a shipped slice in days, with a team that talks about your users.

For Java developers

Stuck "on the backend", switching contexts, and tired of JavaScript churn.

Own features end to end in the language you know, with agents doing the boilerplate.

How the team works

Fixed time, variable scope: a rhythm built for small teams.

Our teams work with the core ideas of Shape Up, the method Basecamp developed for small product teams. It fits the stack almost too well: its standard team is one designer and two programmers, and it builds front end and back end together, one piece at a time. With Wicket's plain HTML templates, the designer works in the same repository as everyone else.

Appetite, not estimates

Instead of estimating stories, decide how much time a problem is worth: two weeks or six. The date stays fixed and the scope flexes to fit it. No story points, no velocity charts.

Shaped before it's built

Experienced people shape the work first: the problem, a rough solution, the rabbit holes and what's out of scope. The pitch is the bet. Once the team takes it on, it becomes requirements in the slices it touches, and those stay when the cycle ends. Agents build fast; shaping and the spec make sure they build the right thing.

Whole scopes, one piece at a time

The team gets the whole problem, not a task list. Its scopes map naturally to slices and modules, and it gets one piece working end to end, page to database, first.

Bets, not backlogs

In each cool-down, decide which shaped pitches to bet on next. No backlog to groom. Work that doesn't ship within its cycle isn't extended by default; it gets reshaped.

Shape Up was written by Ryan Singer at Basecamp and is free to read at basecamp.com/shapeup. We use its core ideas and adapt the details to each team.

The toolbox

Small, open-source tools that take away the friction of day one.

The approach runs on standard Spring and Wicket. These helpers make the first hour pleasant and are deliberately small, so they are easy to understand and to keep up to date.

start.fullstack4j.dev

fullstack4j start

Everything start.spring.io offers, plus Apache Wicket and the whole fullstack4j stack. Our link preselects fullstack4j: Wicket, MongoDB, Spring Modulith and Testcontainers in two starters.

dev.jbaby · fullstack4j-spring-boot-starter

fullstack4j Spring Boot Starter

The whole stack in two dependencies: Wicket with Wicket Oat, Spring Modulith and MongoDB at versions that work together, plus a test starter with a MongoDB replica set and the fullstack4j architecture rules.

dev.jbaby · wicket-spring-boot-starter

Wicket Spring Boot Starter

Apache Wicket 10 on Spring Boot 4 with a single dependency: auto-configured filter, @SpringBean injection, DevTools support. Small enough to read in one sitting.

dev.jbaby · wicket-oat

Wicket Oat

A modern, themeable component library for Wicket: forms, tables, dialogs, toasts and more, with accessible forms by default and light and dark themes.

dev.jbaby · wicket-testkit

wicket-testkit

Page tests that survive refactoring: find components by type, wicket:id, label and the row of a list instead of by path, and compare whole tables with an example. Failures say what was searched and what came close.

agent skill · fullstack4j

fullstack4j skill

Teaches your coding agent this way of working: it writes a slice's spec with you, then a test for every requirement, then the code, until ./mvnw verify is green. Works with Claude Code and other agents that load skills.

terminal
curl https://start.fullstack4j.dev/starter.zip -o shop.zip \
     -d javaVersion=25 -d dependencies=fullstack4j \
     -d groupId=com.acme -d artifactId=shop -d name=shop -d packageName=com.acme.shop
unzip shop.zip && ./mvnw spring-boot:test-run   # the app on a throwaway MongoDB in Docker
Who's behind it

A cooperative of senior engineers, not a one-person show.

fullstack4j is an offering of allwerker eG, an IT cooperative founded in Munich in 2023. Its members are independent senior engineers, architects and consultants who run the cooperative together as equal partners. You work directly with the people who deliver.

allwerker
19senior engineers, architects and consultants
Since 2023a registered cooperative in Munich
Solo or as a teamone expert or a whole team, as you need

Full-stack & Java engineers

Build slices end to end, from the Wicket page to the database, with the habits that keep them clean.

Software architects

Domain- and event-driven design, sensible module cuts, and replacing legacy systems step by step.

Platform & cloud engineers

Run the one deployable wherever it fits: a single server with Docker and Kamal, or Kubernetes on AWS, Google Cloud or Azure.

Security & compliance

Secure development, identity and access management, ISO 27001 and GDPR, built in from the first slice.

AI & data

AI platforms, machine learning and data engineering for when the product needs more than coding agents.

Operations & stability

Monitoring, incident response and performance work, so the product stays up long after launch.

No single point of failure

A team of three can't afford to stall when one person is out. The cooperative covers absences and brings in a specialist for a week when a slice needs one.

Organised the way we ask you to work

Equal partners, shared responsibility, no middle management. The cooperative runs on the same principles as the small teams it builds.

A partner beyond the project

allwerker aims for long-term partnerships, so the people who set up your team are still around when you need them again.

Portrait of Daniel Bartl
Your first contact

Daniel Bartl leads fullstack4j at allwerker.

Software engineer and agile practitioner in Munich. In 2004 he founded a Java and Spring consultancy and grew it to more than a hundred people in self-managing teams without middle management, until it was acquired in 2021. He has organised the Lightweight Java User Group since 2010 and maintains the fullstack4j toolbox.

20+ yearsbuilding on the JVM
100+ peoplecompany built on self-managing teams
Since 2010running a Java user group
Work with allwerker

Set up the team with us, or let us be the team.

Tools are the easy part. What makes a small team work is a shared way of slicing the product, a few firm conventions and the habit of shipping in small steps. allwerker builds that with your people, and brings its own engineers whenever you need more hands.

1–2 days

Fit check

A workshop on your product and your team. We map the domain into slices with Event Modeling, shape the first pitch, look at what you already run, and give you an honest go or no-go.

A slice map, a first shaped pitch with the spec for its first slice, and a clear decision.
Up to 6 weeks

First cycle

We set up the repository, modules, architecture tests, CI and hosting, plus the spec-and-test loop your agents work in, and ship the first shaped project within its appetite, together with the people who will own it.

A running product and a team that knows how to shape and ship the next piece.
Ongoing, part-time

Run & coach

We stay on as pairing and mob partners, or as the team itself, running the cycles with you: shaping, betting, building, cool-down. You decide how far we step back.

A team that keeps its pace, with or without us.

Who builds it

The method stays the same. Who writes the code is up to you.

Your team, coached

Your developers build the product. allwerker sets up the skeleton, pairs and coaches, and steps back as your team takes over.

Mixed team

One or two allwerker engineers join your people, fill the gaps and pass the habits on by working side by side.

allwerker team

A complete team of three from the cooperative builds your product, then hands it over or keeps running it, as you prefer.

In preparation

Trainings

Hands-on courses for teams that want to learn the approach on their own terms, as part of allwerker's training and workshop offering. Get notified when the first dates are set.

Notify me
  • Apache Wicket for Spring developers 1 day
  • Modular monoliths with Spring Modulith & ArchUnit 1 day
  • Spec-driven slices with coding agents 1 day
Honest fit

When it's the right call, and when it isn't.

No approach fits everything. Here is where this one shines and where we'd tell you to choose something else.

A great fit

  • Business applications, internal tools, B2B SaaS, back-office and portals
  • Products full of forms, tables, workflows and rules
  • Teams with Java skills, or a JVM landscape they want to build on
  • Legacy systems you want to replace slice by slice

Probably not

  • Offline-first apps or real-time collaborative editors
  • Consumer apps that need to feel native on every device
  • Content and marketing sites, where a static site generator is simpler
  • Teams with no JVM experience and no wish to build it
FAQ

Questions we hear a lot.

Who will actually work with us?

Daniel Bartl leads fullstack4j and is your first contact. The engineers come from allwerker, chosen for what your product needs: full-stack and Java engineers for the slices, platform and cloud engineers to run them, and security or data specialists when a slice calls for it. You work with them directly.

Do you work in sprints?

We work in Shape Up-style cycles: shaped work, a fixed time box and a cool-down in between. If your organisation runs Scrum or Kanban, we adapt. The slice structure works with any of them.

Isn't Apache Wicket old technology?

It is mature: an Apache project with two decades of production use. Wicket 10 runs on Jakarta EE 10, the current Spring Boot and Java 25, which is what we build on. "Old" mostly means it has kept working while other frameworks were rewritten. That is exactly what you want under a product you plan to keep for ten years.

Can a server-rendered UI feel modern?

Yes. Wicket updates parts of a page via Ajax without you writing JavaScript, and Wicket Oat gives you modern, themeable, accessible components. Where a page really needs a rich client widget, such as a chart, a map or an editor, you embed it into that page and leave the rest alone.

Why MongoDB and not a relational database?

A slice's data usually has one natural shape, like an order with its lines. MongoDB stores it as one document, exactly as the code models it, so the schema grows with the slice instead of through migration scripts. Testcontainers gives every developer and every CI run a real MongoDB, with nothing to install. If your domain is strongly relational or the database is already decided, Spring Data JPA fits the same slice structure.

Can slices call each other directly?

Yes, when an event doesn't fit, for example when a page has to show another slice's data right away. Events stay the first choice because the slice that publishes them doesn't need to know who listens. A slice that has to answer directly puts an interface and the records it returns in its base package. The implementation stays in internal, next to the MongoDB documents, so both can change without breaking anyone. Spring Modulith fails the build when anything else in the slice is used.

What happens when we grow beyond three people?

Modules become team boundaries. Add a second small team that owns a group of slices, not a layer. Spring Modulith keeps the boundaries explicit, so if a module ever really needs to become its own service, the cut is already there.

We already run a SPA and microservices. Do we start over?

No. New slices go into a modular application next to what you have, and old parts are replaced one at a time when there's a reason to touch them. Incremental modernisation of legacy systems is part of what we do.

Why Wicket and not Vaadin or HTMX?

Both are good choices with the same philosophy, and we're happy to talk about them. We prefer Wicket for its plain HTML templates, its strong component model and how easy it is to unit-test UI with WicketTester.

How much lock-in is there?

Very little. Everything is Apache-licensed open source, and the fullstack4j helpers are thin on purpose. You can drop them at any time and keep plain Wicket and plain Spring Boot.

Do we have to use AI agents?

No. The approach stands on its own and was useful long before coding agents. Agents make a small team even more effective, and the architecture tests make them safe to use. How far you go is up to you. The spec in each slice pays off without them too: it is the first thing a new colleague reads.

Isn't a spec just more documentation to keep up?

Not this kind. It lives in the slice's own package-info.java, so it moves and refactors with the code, and every requirement has a test that carries its id. When spec and tests disagree, the build fails. If you need Markdown for a portal or for agents, generate it from the code with jmarkdoc instead of writing a second copy.

Let's talk

Could your next product be built by a team of three?

Tell us what you're building and who's building it. We'll tell you honestly whether this approach fits, where we'd start, and who from allwerker should be involved.