Wicket 10 · Spring Modulith 2.1 · MongoDB · Spring Boot 4.1

The fullstack4j stack, in two dependencies.

Apache Wicket with Wicket Oat, Spring Modulith and MongoDB, at versions that work together. The test starter next to it brings @WicketTest, Spring Modulith’s test support, and a MongoDB that Spring Modulith can actually run on.

One Wicket version Events on MongoDB, out of the box A replica set for tests
pom.xml
<dependency>
  <groupId>dev.jbaby</groupId>
  <artifactId>fullstack4j-spring-boot-starter</artifactId>
  <version>0.1.0</version>
</dependency>

<dependency>
  <groupId>dev.jbaby</groupId>
  <artifactId>fullstack4j-spring-boot-starter-test</artifactId>
  <version>0.1.0</version>
  <scope>test</scope>
</dependency>
Why this starter

A dozen dependencies and two traps, settled once.

A fullstack4j application builds every feature as one Spring Modulith module that holds its Wicket pages, its use cases, its MongoDB documents and its events. Putting that stack together by hand takes about a dozen dependencies, and two of the steps fail in ways that are hard to trace back: an older Wicket slipping in through Wicket Oat, and Spring Modulith’s events failing on MongoDB’s defaults.

These starters settle both once. They hold no logic of their own beyond one default property and one test configuration, so everything past that point is ordinary Wicket, Spring Modulith and Spring Data MongoDB.

2

Dependencies

Instead of about a dozen, plus two dependencyManagement entries and their version properties.

1

Wicket version

Wicket Oat brings an older Wicket with it. The starter keeps every Wicket artifact on the same version.

0

Settings to add

The UUID representation Spring Modulith needs, and a MongoDB replica set for tests and development, come with it.

Only need Wicket on Spring Boot? The Wicket Spring Boot Starter stays the smallest thing that runs Wicket on Spring Boot, for any application. This starter is the opinionated bundle on top of it, for the fullstack4j stack in particular. The approach behind it is on fullstack4j.dev.

Get started

From an empty directory to events between modules.

You need JDK 17 to 25 and a running Docker for the database.

Generate a project

Tick fullstack4j on start.fullstack4j.dev, or ask it from the command line. The pom gets the two starters, and the test sources a TestShopApplication and a first test that already use a MongoDB replica set.

No need to tick Wicket, MongoDB, Spring Modulith or Testcontainers as well: the starters bring all four. Prefer an existing Spring Boot 4.1 project? Add the two dependencies at the top of this page.

Terminal
mkdir shop && cd shop
curl https://start.fullstack4j.dev/starter.zip -o shop.zip \
     -d dependencies=fullstack4j -d javaVersion=25 \
     -d artifactId=shop -d name=shop
unzip shop.zip

Run it on a throwaway MongoDB

TestShopApplication starts the application from the test classpath with MongoDbReplicaSetConfiguration: mongo:8 in Docker, as a replica set, connected through @ServiceConnection. Nothing of it reaches the jar you deploy.

Run ./mvnw spring-boot:test-run and open http://localhost:8080.
TestShopApplication.java
public class TestShopApplication {

    public static void main(String[] args) {
        SpringApplication.from(ShopApplication::main)
            .with(MongoDbReplicaSetConfiguration.class)
            .run(args);
    }
}

Connect modules with events

One module publishes an event inside its transaction, another reacts to it in a transaction of its own. Spring Modulith’s event publication registry keeps the event in MongoDB until the listener is done.

No UUID setting and no replica-set setup: both come with the starters.
billing/internal/InvoiceRaiser.java
@Component
class InvoiceRaiser {

    private final InvoiceRepository invoices;

    InvoiceRaiser(InvoiceRepository invoices) {
        this.invoices = invoices;
    }

    @ApplicationModuleListener
    void on(OrderPlaced order) {
        invoices.save(new Invoice(null,
                order.customer(), order.amount()));
    }
}
What’s inside

The whole stack, at versions that work together.

Every library arrives at the version the others were tested with. The starters add one default and one test configuration, and nothing else.

Wicket on Spring Boot

The Wicket Spring Boot Starter: the Wicket filter, @SpringBean injection, Spring MVC and an embedded Tomcat.

Wicket Oat

Components and themes for the UI: data tables, forms, empty states and toasts, in light and dark.

Spring Modulith

Feature modules with boundaries that are checked: verify() fails the build when one module reaches into another’s internals.

Events through MongoDB

Spring Modulith’s event publication registry stores every event in MongoDB until its listeners are done.

Spring Data MongoDB

Documents and repositories, with the UUID representation Spring Modulith needs already set.

One Wicket version

The starter declares wicket-core and wicket-extensions itself, and Maven picks the declaration nearest to your project.

What each starter brings

The test starter brings exactly what start.fullstack4j.dev adds to a project with Wicket, Spring Data MongoDB, Testcontainers and Spring Modulith ticked one by one.

StarterBrings
fullstack4j-spring-boot-starterwicket-spring-boot-starter, wicket-oat-core, wicket-core and wicket-extensions at one version, spring-boot-starter-data-mongodb, spring-modulith-starter-core, spring-modulith-starter-mongodb
fullstack4j-spring-boot-starter-testwicket-spring-boot-starter-test (WicketTester, @WicketTest, Spring Boot’s test starter), spring-boot-starter-data-mongodb-test, spring-modulith-starter-test (ArchUnit included), spring-boot-testcontainers, testcontainers-junit-jupiter, testcontainers-mongodb, and MongoDbReplicaSetConfiguration
Testing

A real MongoDB in every test, and events that actually run.

Spring Modulith runs every @ApplicationModuleListener in a transaction, and MongoDB only supports transactions on a replica set. A plain Testcontainers MongoDB is a single node, on which the first event fails. MongoDbReplicaSetConfiguration starts mongo:8 as a replica set instead. Import it wherever a test touches MongoDB.

Test pages against MongoDB

@WicketTest starts your application without a servlet container and hands every test a WicketTester with @SpringBean injection.

Spring caches test contexts, so all tests that share a context share one container.

InvoicesPageTests.java
@WicketTest
@Import(MongoDbReplicaSetConfiguration.class)
class InvoicesPageTests {

    @Test
    void showsAnEmptyState(WicketTester tester) {
        tester.startPage(InvoicesPage.class);

        tester.assertContains("No invoices yet");
    }
}

Test a module and its events

@ApplicationModuleTest starts only the module under test. A Scenario publishes an event and waits until the listener has changed the state you are interested in.

billing/InvoicesTests.java
@ApplicationModuleTest
@Import(MongoDbReplicaSetConfiguration.class)
class InvoicesTests {

    @Autowired InvoiceRepository invoices;

    @Test
    void placedOrderGetsAnInvoice(Scenario scenario) {
        scenario.publish(new OrderPlaced("ACME", 42))
            .andWaitForStateChange(invoices::findAll,
                all -> !all.isEmpty())
            .andVerify(all -> assertThat(all)
                .extracting(Invoice::customer)
                .containsExactly("ACME"));
    }
}

Keep the modules apart

One line checks that no module uses another module’s internals and that there are no cycles between modules. ArchUnit comes with Spring Modulith’s test starter.

ArchitectureTests.java
class ArchitectureTests {

    ApplicationModules modules =
        ApplicationModules.of(ShopApplication.class);

    @Test
    void modulesStayOutOfEachOthersInternals() {
        modules.verify();
    }
}

Moving an older project over? Replace its Wicket, Wicket Oat, Spring Modulith, MongoDB and Testcontainers dependencies with the two starters, and remove the spring-modulith-bom import and the version properties. Then delete its own TestcontainersConfiguration, which starts a single MongoDB node, and import MongoDbReplicaSetConfiguration wherever that was imported.

Configuration

One default, and yours always wins.

Spring Modulith’s event publication registry stores UUIDs, which the MongoDB driver refuses to encode until a UUID representation is set. The starter sets it with the lowest precedence of all property sources.

PropertyDefaultWhat it does
spring.mongodb.representation.uuidstandardHow the MongoDB driver encodes UUIDs. A value of your own, in application.properties, as the environment variable SPRING_MONGODB_REPRESENTATION_UUID or on the command line, wins over the default.

Without any value, publishing the first event fails with “The uuidRepresentation has not been specified, so the UUID cannot be encoded”. Spring Boot 4 names the property spring.mongodb.representation.uuid; the old spring.data.mongodb.uuid-representation no longer has any effect.

Development console · optional

Look inside the running application with BootUI.

BootUI is a local developer console for Spring Boot. It is not part of the starters, because it is a development tool, and Spring Boot’s build plugin would package it into your production jar.

Add it in test scope

spring-boot:test-run starts the application from the test classpath, so the console is there while you develop and can never reach the jar you deploy.

BootUI switches itself on in the dev and local profiles. Activate one in TestShopApplication, and the console is served at /bootui, next to your Wicket pages. Your tests run without the profile and stay unaffected.

pom.xml
<dependency>
  <groupId>com.julien-dubois.bootui</groupId>
  <artifactId>bootui-spring-boot-starter</artifactId>
  <version>2.1.0</version>
  <scope>test</scope>
</dependency>
TestShopApplication.java
SpringApplication.from(ShopApplication::main)
    .with(MongoDbReplicaSetConfiguration.class)
    .withAdditionalProfiles("dev")
    .run(args);
  • Health, logs and exceptionsA log tail, recent exceptions and HTTP exchanges, without leaving the browser.
  • Startup, beans and JVMWhere startup time goes, which beans exist, and how memory and threads look.
  • An MCP server for agentsA local MCP server lets a coding agent look into the running application.

Its database panels are built for JDBC databases and don’t cover MongoDB. BootUI also switches itself on whenever spring-boot-devtools is on the classpath; if you add DevTools, set bootui.enabled=false for your tests.

Compatibility

One Spring Boot line per release, tested end to end.

Each Spring Modulith line belongs to one Spring Boot line, so each starter release supports one. An integration test shaped like a freshly generated project, depending on nothing but the two starters, renders a page built from Wicket Oat components, sends an event from one module to another through MongoDB, runs verify(), and fails the build if two Wicket artifacts resolve to different versions.

Starter
0.1.x
Spring Boot
4.1
Spring Modulith
2.1
Apache Wicket
10.11
Wicket Oat
0.3
Java
17 to 25

Wicket arrives through the Wicket Spring Boot Starter 0.2. Java 26 and later need Wicket 10.12, which fixes how Wicket creates its @SpringBean proxies there; this starter moves on once the Wicket starter does.

Not included

Left out on purpose.

The starters bring what every fullstack4j application needs. These stay your choice.

  • Docker Composefullstack4j starts its development database with Testcontainers. A starter can’t ship your compose.yaml, and Spring Boot’s generated one starts a single MongoDB node.
  • Spring Boot DevToolsA development tool, so yours to add. It works with the Wicket starter out of the box.
  • Architecture and spec testsThe fullstack4j skill ships ArchitectureTests and SpecTraceabilityTests to copy into a project.