Exercise - Deploy a Jakarta EE application to JBoss EAP on Azure App Service
In this unit, you deploy a Jakarta EE application to Red Hat JBoss Enterprise Application Platform (JBoss EAP) on Azure App Service. You use the Maven Plugin for Azure App Service to configure the project, compile and deploy the application, and configure a data source.
Configure the app
Configure the app with the Maven Plugin for Azure App Service by using the following steps:
Run the config goal of the Azure plug-in interactively by using the following command:
./mvnw com.microsoft.azure:azure-webapp-maven-plugin:2.13.0:configImportant
If you change the region of your MySQL server, you should match that region to the region of your Jakarta EE application server in order to minimize latency delays.
Use the values in the following table to answer the interactive prompts:
Input element Value Create new run configuration (Y/N) [Y]:YDefine value for OS [Linux]:LinuxDefine value for javaVersion [Java 17]:1: Java 17Define value for runtimeStack:3: Jbosseap 7Define value for pricingTier [P1v3]:P1v3Confirm (Y/N) [Y]:YThe following output is typical:
[INFO] Saving configuration to pom. [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 03:00 min [INFO] Finished at: 2025-02-21T06:24:11+09:00 [INFO] ------------------------------------------------------------------------After you use the Maven command, the following example is a typical addition to your Maven pom.xml file:
<build> <finalName>ROOT</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.4.0</version> </plugin> <plugin> <groupId>com.microsoft.azure</groupId> <artifactId>azure-webapp-maven-plugin</artifactId> <version>2.13.0</version> <configuration> <schemaVersion>v2</schemaVersion> <resourceGroup>jakartaee-app-on-jboss-rg</resourceGroup> <appName>jakartaee-app-on-jboss</appName> <pricingTier>P1v3</pricingTier> <region>centralus</region> <runtime> <os>Linux</os> <javaVersion>Java 17</javaVersion> <webContainer>Jbosseap 7</webContainer> </runtime> <deployment> <resources> <resource> <directory>${project.basedir}/target</directory> <includes> <include>*.war</include> </includes> </resource> </resources> </deployment> </configuration> </plugin> </plugins> </build>Check the
<region>element in your pom.xml file. If its value doesn't match the installation location of MySQL, change it to the same location.Use the following example to modify the
webContainervalue in your pom.xml file toJbosseap 8, for the JBoss EAP 8 environment on Azure App Service:Tip
As of February 2025, the latest available version of JBoss EAP is 8.0 Update 4.1.
<runtime> <os>Linux</os> <javaVersion>Java 17</javaVersion> <webContainer>Jbosseap 8</webContainer> <!-- Change this value --> </runtime>Add the following XML to the
<resources>element of your pom.xml file. This configuration is used to deploy the startup file, which you update later in this unit.<resource> <type>startup</type> <directory>${project.basedir}/src/main/webapp/WEB-INF/</directory> <includes> <include>createMySQLDataSource.sh</include> </includes> </resource>The resource
<type>value ofstartupdeploys the specified script as the startup.sh file for Linux or startup.cmd for Windows. The deployment location is /home/site/scripts/.Note
You can choose the deployment option and deployment location by specifying
typein one of the following ways:type=wardeploys the WAR file to /home/site/wwwroot/app.war ifpathisn't specified.type=war&path=webapps/<appname>deploys the WAR file to /home/site/wwwroot/webapps/<appname>.type=jardeploys the WAR file to /home/site/wwwroot/app.jar. Thepathparameter is ignored.type=eardeploys the WAR file to /home/site/wwwroot/app.ear. Thepathparameter is ignored.type=libdeploys the JAR to /home/site/libs. You must specifypathparameter.type=staticdeploys the script to /home/site/scripts. You must specify thepathparameter.type=startupdeploys the script as startup.sh on Linux, or startup.cmd on Windows. The script is deployed to /home/site/scripts/. Thepathparameter is ignored.type=zipunzips the .zip file to /home/site/wwwroot. Thepathparameter is optional.
Check the values for the
resourceGroupandappNameelements in your pom.xml file.Assign the values for
resourceGroupandappNameto environment variables by using the following commands:export RESOURCE_GROUP_NAME=<resource-group> export WEB_APP_NAME=<app-name>
Compile and build the Jakarta EE app
After you configure the Azure App Service deployment settings, compile and package the source code by using the following command:
./mvnw clean package
The following output is typical:
[INFO] --- war:3.4.0:war (default-war) @ jakartaee-app-on-jboss ---
[INFO] Packaging webapp
[INFO] Assembling webapp [jakartaee-app-on-jboss] in [/private/tmp/mslearn-jakarta-ee-azure/target/ROOT]
[INFO] Processing war project
[INFO] Copying webapp resources [/private/tmp/mslearn-jakarta-ee-azure/src/main/webapp]
[INFO] Building war: /private/tmp/mslearn-jakarta-ee-azure/target/ROOT.war
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.881 s
[INFO] Finished at: 2025-02-21T06:32:30+09:00
[INFO] ------------------------------------------------------------------------
Deploy the Jakarta EE app to JBoss EAP on Azure App Service
After you compile and package the code, deploy the application by using the following command:
./mvnw azure-webapp:deploy
You should see output that includes a success message and the URL of the deployed application. Be sure to save aside the URL for later use.
Configure a database connection
The sample application connects to your MySQL database and displays data. The Maven project configuration in the pom.xml file specifies the MySQL JDBC driver as shown in the following example:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-jdbc-driver}</version>
</dependency>
As a result, JBoss EAP automatically installs the JDBC driver ROOT.war_com.mysql.cj.jdbc.Driver_9_2 to your deployment package ROOT.war.
Create the MySQL DataSource object in JBoss EAP
To access Azure Database for MySQL, you need to configure the DataSource object in JBoss EAP and specify the Java Naming and Directory Interface (JNDI) name in your source code. To create a MySQL DataSource object in JBoss EAP, you use the /WEB-INF/createMySQLDataSource.sh startup shell script. The following example shows an unconfigured version of the script already in Azure App Service:
#!/bin/bash
# In order to use the variables in CLI scripts
# https://access.redhat.com/solutions/321513
sed -i -e "s|.*<resolve-parameter-values.*|<resolve-parameter-values>true</resolve-parameter-values>|g" /opt/eap/bin/jboss-cli.xml
/opt/eap/bin/jboss-cli.sh --connect <<EOF
data-source add --name=JPAWorldDataSourceDS \
--jndi-name=java:jboss/datasources/JPAWorldDataSource \
--connection-url=${AZURE_MYSQL_CONNECTIONSTRING}&characterEncoding=utf8&sslMode=REQUIRED&serverTimezone=UTC&authenticationPlugins=com.azure.identity.extensions.jdbc.mysql.AzureMysqlAuthenticationPlugin \
--driver-name=ROOT.war_com.mysql.cj.jdbc.Driver_9_2 \
--min-pool-size=5 \
--max-pool-size=20 \
--blocking-timeout-wait-millis=5000 \
--enabled=true \
--driver-class=com.mysql.cj.jdbc.Driver \
--jta=true \
--use-java-context=true \
--valid-connection-checker-class-name=org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker \
--exception-sorter-class-name=com.mysql.cj.jdbc.integration.jboss.ExtendedMysqlExceptionSorter
exit
EOF
Note
When you create the datasource, you don't specify a password for the MySQL connection. The environment variable AZURE_MYSQL_CONNECTIONSTRING is specified in the --connection-url parameter. This environment variable is automatically set when the service connection is created later.
The service connection value is set to jdbc:mysql://$MYSQL_SERVER_INSTANCE.mysql.database.azure.com:3306/world?serverTimezone=UTC&sslmode=required&user=aad_jbossapp, which uses the aad_jbossapp username without a password.
By appending &authenticationPlugins=com.azure.identity.extensions.jdbc.mysql.AzureMysqlAuthenticationPlugin to this URL, Microsoft Entra ID authentication is enabled for the aad_jbossapp user.
Configure your App Service instance to invoke the startup script by using the following command:
az webapp config set \
--resource-group ${RESOURCE_GROUP_NAME} \
--name ${WEB_APP_NAME} \
--startup-file '/home/site/scripts/startup.sh'
After the script runs, the application server invokes it every time the application server is restarted.
Note
If your deployment artifact isn't ROOT.war, change the --driver-name=YOUR_ARTIFACT.war_com.mysql.cj.jdbc.Driver_9_2 value, too.
Configure the service connection for MySQL flexible server
After you configure the startup script, configure App Service to use Service Connector for the MySQL flexible server connection by using the following steps:
Set environment variables by using the following commands:
export PASSWORDLESS_USER_NAME_SUFFIX=jbossapp export SOURCE_WEB_APP_ID=$(az webapp list \ --resource-group $RESOURCE_GROUP_NAME \ --query "[0].id" \ --output tsv) export MYSQL_ID=$(az mysql flexible-server list \ --resource-group $RESOURCE_GROUP_NAME \ --query "[0].id" \ --output tsv) export TARGET_MYSQL_ID=$MYSQL_ID/databases/world export MANAGED_ID=$(az identity list \ --resource-group $RESOURCE_GROUP_NAME \ --query "[0].id" \ --output tsv)The environment variables are used for the following purposes:
PASSWORDLESS_USER_NAME_SUFFIXis the suffix for the username used to connect to the MySQL flexible server. The username created has the prefixaad_followed by the specified suffix.SOURCE_WEB_APP_IDis the ID of the Azure App Service instance used to connect to the MySQL flexible server.MYSQL_IDis the ID of the MySQL flexible server.TARGET_MYSQL_IDspecifies the database name as$MYSQL_ID/databases/worldto establish a connection with a user who has permission to access theworlddatabase.MANAGED_IDis the managed identity used to connect to the MySQL flexible server.
Add the extension for
serviceconnector-passwordlessand create the service connection by using the following commands:az extension add \ --name serviceconnector-passwordless \ --upgrade az webapp connection create mysql-flexible \ --resource-group ${RESOURCE_GROUP_NAME} \ --connection $PASSWORDLESS_USER_NAME_SUFFIX \ --source-id $SOURCE_WEB_APP_ID \ --target-id $TARGET_MYSQL_ID \ --client-type java \ --system-identity mysql-identity-id=$MANAGED_IDNote
if you get an error message like
Resource '********-****-****-****-************' does not exist or one of its queried reference-property objects are not present., re-run the command after a few seconds.At the SQL prompt, check the list of users registered in MySQL by using the following query:
SELECT user, host, plugin FROM mysql.user;The following output is typical:
+----------------------------------+-----------+-----------------------+ | user | host | plugin | +----------------------------------+-----------+-----------------------+ | aad_jbossapp | % | aad_auth | | azureuser | % | mysql_native_password | | $CURRENT_AZ_LOGIN_USER_NAME#EXT#@| % | aad_auth | | azure_superuser | 127.0.0.1 | mysql_native_password | | azure_superuser | localhost | mysql_native_password | | mysql.infoschema | localhost | caching_sha2_password | | mysql.session | localhost | caching_sha2_password | | mysql.sys | localhost | caching_sha2_password | +----------------------------------+-----------+-----------------------+ 8 rows in set (2.06 sec)You should see an
aad_jbossappuser that uses theaad_authplugin. From JBoss EAP deployed on Azure, you can connect to the MySQL flexible server using theaad_jbossappusername without a password.
Confirm the DataSource reference in the code
To access the MySQL database from your application, you need to configure the data source reference in your application project.
The database access code is implemented using the Java Persistence API (JPA). The configuration for the DataSource reference is in the JPA configuration file persistence.xml.
Use the following steps to confirm the DataSource reference:
Open the src/main/resources/META-INF/persistence.xml file and check to see if the
DataSourcename matches the name used in the configuration. The startup script already created the JNDI name asjava:jboss/datasources/JPAWorldDataSource, as shown in the following example:<persistence-unit name="JPAWorldDatasourcePU" transaction-type="JTA"> <jta-data-source>java:jboss/datasources/JPAWorldDataSource</jta-data-source> <exclude-unlisted-classes>false</exclude-unlisted-classes> <properties> <property name="hibernate.generate_statistics" value="true" /> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" /> </properties> </persistence-unit>Access the MySQL database in the
PersistenceContextunit name as shown in the following example:@Transactional(REQUIRED) @RequestScoped public class CityService { @PersistenceContext(unitName = "JPAWorldDatasourcePU") EntityManager em;
Access the application
The sample application implements three REST endpoints. To access the application and retrieve data, use the following steps:
Use your browser to navigate to the application URL, which was shown in the output when you deployed the application.
To get all the continent information in JSON format, use the
GETmethod on theareaendpoint.To get all the countries and regions in a specified continent, use the
GETmethod on theareaendpoint and specify acontinentpath parameter.To get all the cities that have a population greater than one million within the country or region specified, use the
GETmethod on thecountriesendpoint and specify acountrycodepath parameter.
Exercise summary
In this unit, you validated the application REST endpoints and confirmed that your application can get data from your MySQL database. In the next unit, you examine the server logs.