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>dev.jbaby</groupId>
  <artifactId>wicket-spring-boot-starter</artifactId>
  <version>0.1.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.

Being small also makes it a good way to teach Wicket: the whole auto-configuration fits on a screen, so nothing about how Wicket and Spring Boot meet stays hidden. Add it to your own Spring Initializr and a new Wicket project is one click away.

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.

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 jar, wicketstuff modules included, 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

Every release is tested against one Wicket version.

Each release pins a Wicket version and the Spring Boot lines it has been tested with, by an integration test shaped like a freshly generated Spring Boot project.

Starter
0.1.x
Apache Wicket
10.11
Spring Boot
4.0 & 4.1
Java
17 to 25

Java 26 and later need Wicket 10.12, which fixes how Wicket creates its @SpringBean proxies there. The next starter release will move to it.

Spring Initializr

A Wicket checkbox in your own Initializr.

The starter is not listed on start.spring.io, but Spring Initializr is open source. An instance of your own, for a team or a training, offers Wicket next to everything start.spring.io has.

Once

Run Spring Initializr

Start from spring-io/initializr, the code behind start.spring.io.

Per release

Add the Wicket entry

The entry in the README maps each Spring Boot line to the starter release tested with it.

Every project

Point your tools at it

IntelliJ IDEA and the Spring Boot CLI accept a custom Initializr URL, so a new Wicket project is one click away.

No Initializr at hand? Generate a Spring Boot project as usual and add the starter by hand: it is the single dependency at the top of this page.