Packaging and deployment
In liberty:dev mode the server lives inside the target folder. For a production system the application has to be packaged into a file and deployed. There are two paths: hand a WAR to an already installed application server — the classic way — or package the application together with the server into one file or image, as is customary in the cloud.
The WAR file
./mvnw packageMaven builds target/blog.war. It holds only your code and resources — about 40 KB, because the implementations of all the specifications live in the server.
| Inside the WAR | What it is |
|---|---|
WEB-INF/classes/ | Compiled classes and META-INF/persistence.xml |
WEB-INF/lib/ | Third-party libraries, if any |
index.html, posts.xhtml | Files from src/main/webapp |
An installed server
On a separately installed Open Liberty there are two ways to deploy a WAR:
| Way | How |
|---|---|
The dropins folder | Copy the WAR to usr/servers/defaultServer/dropins/ — the server picks it up by itself and takes the context root from the file name |
server.xml | Put the WAR into apps/ and declare <webApplication contextRoot="/blog" location="blog.war" /> |
The second way is better: server.xml shows what is deployed and with which settings. The featureManager, the data source and the driver library from our src/main/liberty/config/server.xml move there too.
A runnable JAR
The Liberty plugin can package the server, the configuration and the application into one JAR:
./mvnw package liberty:create liberty:install-feature liberty:deploy liberty:package -Dinclude=runnable[INFO] CWWKM2136I: Package file location is ...\blog\target\blog.jar.
[INFO] BUILD SUCCESS
This file runs on any machine with Java 21 or newer:
java -jar target/blog.jarLaunching defaultServer (Open Liberty 26.0.0.9/wlp-1.0.117.cl260920260824-0859) on OpenJDK 64-Bit Server VM, version 25.0.4.1+1-LTS
[AUDIT ] CWWKT0016I: Web application available (default_host): http://home-pc:8080/blog/
[AUDIT ] CWWKF0011I: The defaultServer server is ready to run a smarter planet. The defaultServer server started in 18.206 seconds.
The output is shortened. On start the JAR unpacks the server into a temporary folder and starts it. It contains only the enabled server features: for the blog the file is about 100 MB.
In liberty:dev mode the server takes the keystore passwords from a generated server.env file. In the runnable JAR login did not work for us: the log had CWWKS4118E: LTPA configuration error. A keysPassword attribute is not configured on the <ltpa /> element, and requests with a password got Error 403: AuthenticationFailed. If the application uses Jakarta Security, set the passwords explicitly in server.xml:
<!-- src/main/liberty/config/server.xml (fragment) -->
<keyStore id="defaultKeyStore" password="changeme" />
<ltpa keysPassword="changeme" />In a production system replace changeme with an environment variable: password="${env.KEYSTORE_PASSWORD}".
Docker
The Open Liberty generator put a Dockerfile into the project:
FROM icr.io/appcafe/open-liberty:kernel-slim-java21-openj9-ubi-minimal
COPY --chown=1001:0 /src/main/liberty/config /config
RUN features.sh
COPY --chown=1001:0 target/*.war /config/apps
RUN configure.shThe image takes a minimal Liberty, copies server.xml, installs the needed features and adds the WAR. The H2 driver also has to go into the image — into the folder that library in server.xml points to.
Other servers
A WAR is portable: the same file deploys to WildFly, Payara or GlassFish. Only the server's own configuration differs.
| Server | Where to put the WAR | Where the data source is configured |
|---|---|---|
| Open Liberty | dropins/ or apps/ | server.xml |
| WildFly | standalone/deployments/ | standalone.xml or the CLI |
| Payara, GlassFish | autodeploy/ or asadmin deploy | The console or asadmin |
The jdbc/blog name from persistence.xml has to be created on each server in its own way — the application code does not change.
Common mistake
Starting a second server instance while the first one (for example liberty:dev in another terminal) is still running. The application formally starts, but the port is taken, and the log gets:
[ERROR ] CWWKO0221E: TCP Channel defaultHttpEndpoint initialization did not succeed. The socket bind did not succeed for host * and port 8080. The port might already be in use. Exception Message: Address already in use: bind
Stop the first server (q in liberty:dev or ./mvnw liberty:stop) or set another httpPort in server.xml.