- Even 5-10 year old java applications can be containerized and migrated to the cloud
- Fargate can run Application Servers, like JBoss (now Wildfly)
- You can run Java Apps on those application servers
- When migrating, the .war files often need changes / library updates, but that shouldn't stop you from migrating the app
- If you don't have the java source anymore, .class files can be decompiled using a variety of tools
Containerizing Java Application Server
If your app uses JBoss/Wildfly, you can use the Wildfly docker image from https://hub.docker.com/r/jboss/wildfly. It's fully maintained, so getting updates is just a matter of rebuilding the docker container in your CI/CD workflow.
Migrating From Old JBoss Versions
It's very probable you'll have to migrate from an ancient JBoss version. Recently, we had to migrate from JBoss 6.1.0.Final to Wildfly 15.0.1.Final. We had to go from JDK 6 to 11, from Hibernate 3.6 to 5.2, and from MySQL 5.1 to 5.7. That all without having the source code. In the next sections of this article, I'll dive a bit deeper in the problems we came across.
New MySQL Datasource
The first issue we had to solve was to migrate from an old type of datasource (used in older JBoss versions) to a newer version of the datasource. This is how our datasource looked like:
<?xml version="1.0" encoding="UTF-8"?>
<datasources>
<local-tx-datasource>
<jndi-name>datasourcename</jndi-name>
<connection-url>jdbc:mysql://localhost:3306/database-name?characterEncoding=UTF-8</connection-url>
<driver-class>com.mysql.jdbc.Driver</driver-class>
<user-name>root</user-name>
<password>...</password>
<min-pool-size>5</min-pool-size>
<max-pool-size>200</max-pool-size>
<valid-connection-checker-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLValidConnectionChecker</valid-connection-checker-class-name>
<idle-timeout-minutes>5</idle-timeout-minutes>
</local-tx-datasource>
</datasources>
These types of datasources don't work anymore in newer JBoss/Wildfly versions, so we added a new MySQL Connector to our Dockerfile, and configured a new type of datasource in standalone.xml. This is the beginning of our Dockerfile:
FROM jboss/wildfly:15.0.0.Final
RUN mkdir -p wildfly/modules/system/layers/base/com/mysql
ADD mysql/mysql-connector-java-8.0.15.jar .
RUN wildfly/bin/jboss-cli.sh --command='module add --name=com.mysql --resources=mysql-connector-java-8.0.15.jar --dependencies=javax.api,javax.transaction.api'
ADD standalone.xml wildfly/standalone/configuration/standalone.xml
In our standalone.xml (copied from within the image), we added the following lines to configure our datasource:
<datasources>
<datasource jndi-name="java:jboss/datasources/datasourcename" pool-name="datasourcename" enabled="true" use-ccm="true" use-java-context="true" jta="true">
<connection-url>jdbc:mysql://MYSQL_HOSTNAME:3306/MYSQL_DATABASE?characterEncoding=UTF-8</connection-url>
<driver>mysql</driver>
<new-connection-sql>SELECT 1</new-connection-sql>
<pool>
<min-pool-size>5</min-pool-size>
<max-pool-size>50</max-pool-size>
</pool>
<security>
<user-name>MYSQL_LOGIN</user-name>
<password>MYSQL_PASSWORD</password>
</security>
<validation>
<check-valid-connection-sql>SELECT 1</check-valid-connection-sql>
</validation>
<timeout>
<xa-resource-timeout>0</xa-resource-timeout>
</timeout>
<statement>
<track-statements>false</track-statements>
</statement>
</datasource>
</datasources>
Credential Configuration
When deploying, we run a entrypoint.sh that replaces the credentials with the contents of environment variables:
#!/bin/bash
sed -i 's#MYSQL_HOSTNAME#'${MYSQL_HOSTNAME}'#' wildfly/standalone/configuration/standalone.xml
sed -i 's#MYSQL_LOGIN#'${MYSQL_LOGIN}'#' wildfly/standalone/configuration/standalone.xml
sed -i 's#MYSQL_PASSWORD#'${MYSQL_PASSWORD}'#' wildfly/standalone/configuration/standalone.xml
sed -i 's#MYSQL_DATABASE#'${MYSQL_DATABASE}'#' wildfly/standalone/configuration/standalone.xml
/opt/jboss/wildfly/bin/standalone.sh -b 0.0.0.0
Full Dockerfile
Now we have all the pieces together to create the full Dockerfile. In our local project directory, we created a deploy/ directory where we can put the .war files that need to be deployed
FROM jboss/wildfly:15.0.0.Final
RUN mkdir -p wildfly/modules/system/layers/base/com/mysql
ADD mysql/mysql-connector-java-8.0.15.jar .
RUN wildfly/bin/jboss-cli.sh --command='module add --name=com.mysql --resources=mysql-connector-java-8.0.15.jar --dependencies=javax.api,javax.transaction.api'
ADD standalone.xml wildfly/standalone/configuration/standalone.xml
ADD deploy/* wildfly/standalone/deployments/
USER root
RUN chown -R jboss:jboss /opt/jboss/wildfly/standalone/deployments/
USER jboss
COPY entrypoint.sh /entrypoint.sh
CMD ["/entrypoint.sh"]
Not There Yet...
Once we launched the app, we hit some more troubles. With the new Hibernate version, old style of writing parameters in queries were not supported anymore:
Caused by: java.lang.IllegalArgumentException: org.hibernate.QueryException: Legacy-style query parameters (`?`) are no longer supported; use JPA-style ordinal parameters (e.g., `?1`) instead : FROM com.company.app para where para.platformId=? and para.type=? [FROM com.company.app para where para.platformId=? and para.type=?]
With no source files, we had to decompile the java .class files first, change the queries, and then recompile them back into .class files. To do that we used the JDCore java decompiler. You can get a command-line JDCore decompiler from https://github.com/nviennot/jd-core-java/. It was just a matter of replacing param=? in the code to param = :param. Here's a snippet:
# old code
String hql = "FROM table where table.id = ?";
Query q = h.createQuery(hql);
q.setParameter(1, platformId);
# new code
String hql = "FROM table where table.id = :id";
Query q = h.createQuery(hql);
q.setParameter("id", platformId);
Dependency Updates
We still had to solve a lot of dependency issues, like updating libraries because they were vulnerable, but luckily the new Wildfly version already has a lot of libraries packaged within the platform (like hibernate).
The Complete Setup
After migrating the application to AWS, this is how the complete setup looks like:
- Source code in bitbucket
- Dockerfile automatically being built by bitbucket pipelines
- Docker image pushed on every commit to ECR
- MySQL migrated to RDS (reduces maintenance to almost zero)
- Wildfly docker image containing the app deployed using ECS Fargate (AWS manages underlying infrastructure, we only maintain the docker image)
- AWS ALB for TLS termination and for request loadbalancing of the app
There are no EC2 instances to manage, no operating systems to keep up-to-date, everything is managed by AWS. We only need to keep our docker image up-to-date. This reduces the maintenance cost dramatically.