Apache Wicket 10 · Spring Boot 4

Apache Wicket on Spring Boot, with a single dependency.

Add one starter and you have a running Wicket application: the filter is registered, @SpringBean injection works everywhere, and every setting lives in application.properties. Then it gets out of your way.

Zero configuration Every default replaceable DevTools ready
<dependency>
  <groupId>org.wicketstuff</groupId>
  <artifactId>wicketstuff-spring-boot-starter</artifactId>
  <version>10.12.0</version>
</dependency>
Why this starter

The same three pieces, every time. Now they come for free.

Running Wicket on Spring Boot always needs a WebApplication, a filter registration for it, and a SpringComponentInjector so that @SpringBean works. None of it is hard, but it is the first thing standing between you and a running Wicket page, and it is easy to get subtly wrong.

The goal goes one step further: Apache Wicket selectable on start.spring.io, the way Vaadin already is, so that a new Wicket project is one click away. See the roadmap.

1

One dependency

Wicket, the Wicket–Spring bridge and an embedded servlet container arrive together. Nothing else to declare.

0

Lines of setup

Start the application and a placeholder page is already served, telling you how to replace it with your own.

∞

Room to grow

Past the setup, it is ordinary Wicket and ordinary Spring, documented by their own projects. The starter stays small on purpose.

Looking for batteries included (Spring Security, WebSockets, bean validation, session datastores and more)? Have a look at MarcGiffing/wicket-spring-boot. Both solve the same problem with opposite philosophies: pick that one for breadth, this one to start from the smallest thing that works. Being part of wicketstuff also means the starter is released with it: use the starter version matching your Wicket version.

Get started

From empty project to your first page in three steps.

Start from any Spring Boot 4 project, or an empty one with just a main class.

Add the starter and run

Add the dependency shown above and start your @SpringBootApplication. There is nothing else to configure: the starter brings the web stack with it.

Open http://localhost:8080: a default Wicket page says “It works!”.
ExampleApplication.java
@SpringBootApplication
public class ExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(ExampleApplication.class, args);
    }
}

Register your own Wicket application

Subclass WebApplication and make it a Spring bean with @Component. The moment it exists, the built-in default page steps aside and your home page is served instead.

WicketApplication.java
@Component
public class WicketApplication extends WebApplication {

    @Override
    public Class<? extends Page> getHomePage() {
        return HomePage.class;
    }

    @Override
    protected void init() {
        super.init();
        // mount pages, configure Wicket as usual...
    }
}

Inject Spring beans into your pages

Use Wicket’s standard @SpringBean on fields of any page, panel or component. Injection is wired up for you, and it already works inside your application’s init().

Pages stay serializable: Wicket injects a lightweight proxy, not the bean itself.
HomePage.java
public class HomePage extends WebPage {

    @SpringBean
    private GreetingService greetingService;

    public HomePage() {
        add(new Label("message", greetingService.greet()));
    }
}
Features

Everything Wicket needs from Spring Boot, and nothing more.

A single auto-configuration that switches itself on for servlet web applications with Wicket on the classpath.

Filter registration

Wicket’s WicketFilter is registered with the embedded servlet container, mapped and named from your settings.

Application discovery

Any WebApplication bean is picked up and bound. Without one, a friendly default page gets you going.

@SpringBean injection

Spring beans in pages, panels and components, and already inside your application’s init().

Externalised settings

Filter path, name, order and Wicket’s runtime mode are plain Spring properties, with autocompletion in your IDE.

Lives alongside Spring MVC

Requests Wicket does not handle fall through to Spring MVC, so your REST controllers keep working next to your pages.

DevTools ready

Automatic restarts work, stored pages restore correctly and sessions survive. See how.

Using the features

Common tasks, one snippet each.

Everything below is optional. The defaults are chosen so that a new project needs none of it.

Add REST endpoints next to your pages

The starter brings Spring MVC along. Wicket’s filter is mapped at /* but forwards anything it does not handle further down the chain, so a @RestController just works.

Prefer a strict separation? Confine Wicket to its own prefix with wicket.filter-path=/app/*.

GreetingController.java
@RestController
class GreetingController {

    // "/" still renders your Wicket home page
    @GetMapping("/api/greeting")
    String greeting() {
        return "hello";
    }
}

Switch to production mode

Like Wicket itself, the starter runs in DEVELOPMENT mode unless told otherwise: it reloads changed markup, keeps wicket:id attributes in the HTML, enables the Ajax debug window, serves unminified JavaScript and shows full stack traces. Great on your machine, not on a server.

Put DEPLOYMENT in a production profile and start with --spring.profiles.active=prod, or set the environment variable WICKET_CONFIGURATION=DEPLOYMENT.

application-prod.properties
wicket.configuration=DEPLOYMENT

Place Wicket in the filter chain

By default Wicket runs last, after every other filter, including Spring Security’s filter chain (order -100). That is usually right: security decisions happen before Wicket renders anything.

If another filter of yours has to run after Wicket, move Wicket up the chain.

application.properties
# lower values run earlier
wicket.filter-order=0

Deploy as a WAR

The embedded Tomcat does not commit you to it. Use Spring Boot’s usual recipe: war packaging, a SpringBootServletInitializer, and the container re-declared at provided scope.

Your direct declaration wins, so Tomcat stays out of WEB-INF/lib while Wicket and the starter are packaged as normal.

pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
</dependency>

Test with a real server

Wicket keys its application registry by filter name, and Spring caches test contexts for the whole JVM. Two tests that each start a real servlet container would collide on the default name, so give each of them its own.

Tests using the default mock environment never start the filter and need nothing.

MyIntegrationTest.java
@SpringBootTest(
        webEnvironment = WebEnvironment.RANDOM_PORT,
        properties = "wicket.filter-name=my-test-filter")
class MyIntegrationTest {
    // ...
}
Configuration

Every setting is a Spring property.

Set them in application.properties, application.yml, environment variables or any other Spring Boot property source. Your IDE autocompletes them.

PropertyDefaultWhat it does
wicket.enabledtrueSet to false to switch the whole auto-configuration off.
wicket.configurationDEVELOPMENTWicket’s runtime mode: DEVELOPMENT or DEPLOYMENT.
wicket.filter-path/*URL pattern the Wicket filter is mapped to, e.g. /app/*.
wicket.filter-namewicket-filterName of the registered filter, which also names the Wicket application.
wicket.filter-orderLOWEST_PRECEDENCEPosition of the Wicket filter in the servlet filter chain; lower values run earlier.

Replace any default with your own bean

Everything the starter contributes steps aside as soon as you define your own, so you can take over as much or as little as you need.

Starter beanSteps aside whenDefine your own to
webApplicationany WebApplication bean existsuse your own Wicket application (the usual case)
springComponentInjectorRegistrarany SpringComponentInjector bean existscontrol how Spring injection is wired
wicketFilterRegistrationa FilterRegistrationBean<WicketFilter> or WicketFilter bean existscontrol every detail of the filter registration

Migrating an existing app? If your init() already calls getComponentInstantiationListeners().add(new SpringComponentInjector(this)), drop that line and let the starter do it, or declare your own SpringComponentInjector bean. Keeping both would register two injectors.

Spring Boot DevTools

Automatic restarts, without broken pages.

Add spring-boot-devtools and keep coding: the application restarts whenever your classes change. The starter makes sure Wicket plays along, with no configuration on your side.

The problem

DevTools runs your classes in a restart classloader, while libraries stay in a base classloader that also holds a stale copy of your classes. Wicket restores stored pages (on the back button, or after a restart) from the base side, but rebuilds their @SpringBean proxies from the restart side.

Base classloader
wicket-*.jarHomePageGreetingService
Restart classloader
HomePageGreetingService

Same class names, different classes. Every stored page with an injected field then fails:

ClassCastException: cannot assign instance of WicketProxy_GreetingService to field HomePage.greetingService of type GreetingService

The fix

The starter ships a META-INF/spring-devtools.properties that moves every Wicket and wicketstuff jar into the restart classloader, right next to your own classes. Pages are now restored against exactly the classes that are running.

Base classloader
spring-*.jartomcat-*.jar…
Restart classloader
wicket-*.jarwicketstuff-*.jarHomePageGreetingService

You do not need to do anything: DevTools reads the file straight from the starter jar.

  • Back button worksOlder pages restore from Wicket’s page store with their injected beans intact.
  • Restarts are cleanThe application is re-created on every restart, with @SpringBean ready in init().
  • Sessions surviveDevTools persists HTTP sessions across restarts, so your Wicket session and everything in it is kept.

Keep page state across restarts, too

Your session survives a restart, but the pages in it do not by default: Wicket stores them in the servlet container’s temporary directory, and Spring Boot creates a new one each time Tomcat starts. An old page is then simply rendered afresh.

Give Tomcat a fixed base directory while developing, and pages keep their state across restarts as well.

application.properties
# development only: keep pages across restarts
server.tomcat.basedir=target/tomcat
Compatibility

Pick the starter version matching your Wicket version.

The starter is released together with wicketstuff, so there is no second release cycle to wait for.

Starter / Wicket
10.12.x
Spring Boot
4.0 & 4.1
Spring Framework
7.0
Java
17+
Roadmap

Next stop: start.spring.io.

The aim is a Wicket checkbox on Spring Initializr, right next to Vaadin, so that a new Wicket project is one click away. The Initializr only offers released versions, so the way there has three steps.

Now

Merge into wicketstuff

The starter becomes a module of wicketstuff-core, built and tested with every change.

Next release

Publish to Maven Central

It ships with the next wicketstuff release, so there is a real version for the Initializr to reference, never a snapshot.

Then

Request the listing

The entry is already prepared in the README; it is submitted to the Spring Initializr team and kept current for every new Spring Boot line.

Until then, generate a Spring Boot project as usual and add the starter by hand: it is the single dependency at the top of this page.