WildFly remote EJB client tutorial

[Updated] In this tutorial you will learn how to configure and deploy an EJB 3 remote client for different versions of JBoss / WildFly, including the latest WildFly 11 version
WildFly 11

Coding a Remote EJB Project for WildFly 11

If you are running WildFly 11 there are good news for you as coding your EJB 3 clients will be quite easier. As a matter of fact, the older jboss-remote-naming protocol has been replaced by a client library named WildFly Naming client which simplifies the JNDI lookup.

Coding the Server project

So assumed you have a simple server project which includes the following Stateful and Stateless EJB:

package com.itbuzzpress.chapter4.ejb;

import javax.ejb.Remote;
import javax.ejb.Stateful;
import com.itbuzzpress.chapter4.exception.InsufficientFundsException;
 
@Stateful
@Remote(Account.class)
public class AccountEJB implements Account {

    long money;
 
    
	@Override
	public long getMoney() {
		return money;

	}

	public void createAccount(long amount)  
	{
		this.money= amount;
		 
	}

	@Override
	public void deposit(long amount)  
	{
		 
			this.money+= amount;
			 
		System.out.println("Money deposit. total is "+money);
	}
 
	 
	@Override
	public void withdraw(long amount) throws InsufficientFundsException {
		
		long newAmount = money - amount;
		if (newAmount < 0) {
			throw new InsufficientFundsException("Unsufficient funds for account! ");
		}
		
		money = newAmount;	 
		System.out.println("Money withdrawal. total is "+money);

	}
}

package com.itbuzzpress.chapter4.ejb;

import javax.ejb.Remote;
import javax.ejb.Stateless;

@Stateless
@Remote(Calculator.class)
public class CalculatorEJB implements Calculator {
   
	float interest=5;
 
	@Override
	public float calculateInterest(long money) {
	 
	    return money * (1+ (interest/100));
	       
   }
	
}

The interfaces for both classes are:

package com.itbuzzpress.chapter4.ejb;

import com.itbuzzpress.chapter4.exception.InsufficientFundsException;

public interface Account {

	public void deposit(long amount);
	public void withdraw(long amount) throws InsufficientFundsException;
	
	public long getMoney();
	public void createAccount(long amount);
}

package com.itbuzzpress.chapter4.ejb;

public interface Calculator {

	public float calculateInterest(long money);
	 
}

Coding the Remote EJB Client

Now let's write an EJB Client code for it:

package com.itbuzzpress.chapter4.client;

import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import com.itbuzzpress.chapter4.ejb.Account;
import com.itbuzzpress.chapter4.ejb.Calculator;
import com.itbuzzpress.chapter4.exception.InsufficientFundsException;

import org.wildfly.naming.client.*; 
import javax.naming.spi.NamingManager;
public class RemoteEJBClient {

	public static void main(String[] args) throws Exception {
		Calculator calculator = lookupCalculatorEJB();
                 

		Account account = lookupAccountEJB();
		System.out.println("Create Account with 1000$ ");

		account.createAccount(1000l);
		System.out.println("Deposit 250$ ");
		account.deposit(250);

		try {
			System.out.println("Withdraw 500$ ");
			account.withdraw(500);
		} catch (InsufficientFundsException e) {

			e.printStackTrace();
		}
		long money = account.getMoney();
		System.out.println("Money left " + money);
		float totalMoney = calculator.calculateInterest(money);
		System.out.println("Money plus interests " + totalMoney);


	}

	private static Account lookupAccountEJB() throws NamingException {
		final Hashtable jndiProperties = new Hashtable();
 
		jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY,  "org.wildfly.naming.client.WildFlyInitialContextFactory");
                jndiProperties.put(Context.PROVIDER_URL,"http-remoting://localhost:8080");
		final Context ctx = new InitialContext(jndiProperties);
 
		  

		return (Account) ctx
				.lookup("ejb:/javaee7-ejb-server-basic/AccountEJB!com.itbuzzpress.chapter4.ejb.Account?stateful");
	}

	private static Calculator lookupCalculatorEJB() throws NamingException {
		final Hashtable jndiProperties = new Hashtable();
		jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY,  "org.wildfly.naming.client.WildFlyInitialContextFactory");
                jndiProperties.put(Context.PROVIDER_URL,"http-remoting://localhost:8080");
		final Context context = new InitialContext(jndiProperties);

		return (Calculator) context
				.lookup("ejb:/javaee7-ejb-server-basic/CalculatorEJB!com.itbuzzpress.chapter4.ejb.Calculator");
	}
}

As you can see, the most evident change with former WildFly ejb client is the new Initial Context which requires "org.wildfly.naming.client.WildFlyInitialContextFactory". If you set the system property java.naming.factory.initial to org.wildfly.naming.client.WildFlyInitialContextFactory, then your Java code to access an initial context is just:

InitialContext ctx = new InitialContext();
Account account = (Account) ctx.lookup("ejb:/javaee7-ejb-server-basic/CalculatorEJB!com.itbuzzpress.chapter4.ejb.Calculator");

The great news, is that now the list of dependencies required is really minimal compared to older WildFly versions:

   <dependencyManagement>
      <dependencies>
         <dependency>
            <groupId>org.wildfly</groupId>
            <artifactId>wildfly-ejb-client-bom</artifactId>
            <version>11.0.0.CR1</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
      </dependencies>
   </dependencyManagement>
   <dependencies>

      <!-- We depend on the EJB remote business interfaces of this application -->
      <dependency>
         <groupId>com.itbuzzpress.chapter4</groupId>
         <artifactId>javaee7-ejb-server-basic</artifactId>
         <type>ejb-client</type>
         <version>${project.version}</version>
      </dependency>

      <dependency>
         <groupId>org.wildfly</groupId>
         <artifactId>wildfly-naming</artifactId>
         <version>11.0.0.CR1</version>
      </dependency>

      <!-- JBoss EJB client API jar. We use runtime scope because the EJB client 
			API isn't directly used in this example. We just need it in our runtime classpath -->
      <dependency>
         <groupId>org.jboss</groupId>
         <artifactId>jboss-ejb-client</artifactId>
         <scope>runtime</scope>
      </dependency>
   </dependencies>

You can checkout the full source code of this project at: https://github.com/fmarchioni/mastertheboss/tree/master/ejb/remote-ejb-wf11

Configuring Security

Another change impacting your clients is that the former jboss-ejb-client.properties file is depecated so you are encouraged to migrate to the Elytron wildfly-config.xml file  which unifies all client configuration in a single place. For the purpose of our example, we will just add a default wildfly-confug.xml file which uses any available SASL mechanism selector for authentication.

<configuration>
    <authentication-client xmlns="urn:elytron:1.0">
        <authentication-rules>
                    <rule use-configuration="default" />
        </authentication-rules>
        <authentication-configurations>
            <configuration name="default">
                <sasl-mechanism-selector selector="#ALL" />
                <set-mechanism-properties>
                    <property key="wildfly.sasl.local-user.quiet-auth" value="true" />
                 </set-mechanism-properties>
                <providers>
                    <use-service-loader/>
                </providers>
             </configuration>
        </authentication-configurations>
    </authentication-client>
</configuration>

On the other hand, if you need to provide credentials, then you can add them in the authentication-configurations block as in the following example:

<configuration>
    <authentication-client xmlns="urn:elytron:1.0">
        <authentication-rules>
            <rule use-configuration="default"/>
        </authentication-rules>
        <authentication-configurations>
            <configuration name="default">
                <sasl-mechanism-selector selector="DIGEST-MD5"/>
                <set-user-name name="ejbuser"/>
                <credentials>
                    <clear-password password="password1!"/>
                </credentials>
            </configuration>
        </authentication-configurations>
    </authentication-client>	
</configuration>

 To create the user on the server, just execute the add-user.sh script as follows:

$ ./add-user.sh -a -u ejbuser -p password1! 

Using pure HTTP transport

To allow for balancing at the individual invocation level, a new “pure” HTTP protocol was included in WildFly 11. Clients which use http:// URLs as opposed to remoting+http:// will be able to use this feature. The advantage of it, is that this protocol utilizes standard HTTP behavior, it can be efficiently balanced by any load-balancer, not just the one built into EAP.

In order to use the pure HTTP Transaport, you have to include the following dependencies in your project:

<dependency>
                <groupId>org.wildfly.wildfly-http-client</groupId>
                <artifactId>wildfly-http-client-common</artifactId>                  
</dependency>
<dependency>
                <groupId>org.wildfly.wildfly-http-client</groupId>
                <artifactId>wildfly-http-naming-client</artifactId>                 
 </dependency>

Then in your code you need to replace the Context.PROVIDER_URL from this:

jndiProperties.put(Context.PROVIDER_URL,"http-remoting://localhost:8080");

to this:

jndiProperties.put(Context.PROVIDER_URL,"http://localhost:8080/wildfly-services");

WildFly 8/9/10 and JBoss AS 7 EJB remote Client

Previous versions of JBoss AS (versions < 7.x) used JNP project as the JNDI naming implementation so developers are familiar with the jnp:// PROVIDER_URL URL in order to communicate with the Application Server. Starting with AS7, the JNP project is not used. The client side of the JNP project has now been replaced by jboss-remote-naming project (https://github.com/jbossas/jboss-remote-naming). There were various reasons why the JNP client was replaced by jboss-remote-naming project. One of them was the JNP project did not allow fine grained security configurations while communicating with the JNDI server. The jboss-remote-naming project is backed by the jboss-remoting project (https://github.com/jboss-remoting/jboss-remoting) which allows much more and better control over security.

Coding the Server classes

We will create a Maven project named ejb-remote-server with the following archetype:

$ mvn -DarchetypeGroupId=org.codehaus.mojo.archetypes -DarchetypeArtifactId=ejb-javaee7 -DarchetypeRepository=https://nexus.codehaus.org/content/repositories/snapshots -DgroupId=com.sample.ejb -DartifactId=ejb-remote-server -Dversion=1.0 -Dpackage=com.sample.ejb -Darchetype.interactive=false --batch-mode --update-snapshots archetype:generate

Let's add some classes to our Project:

package com.sample.ejb;

public interface SampleBeanRemote {
     public String echo(String s);

}

And here's the implementation:

package com.sample.ejb;

import javax.ejb.Remote;
import javax.ejb.Stateless;

@Stateless
@Remote(SampleBeanRemote.class) 
public class  SampleBeanRemoteImpl implements SampleBeanRemote  {

    @Override
    public String echo(String s) {

        return "Hello "+s;
    }


}

The following dependencies will be needed in the Server project:

    <properties>

        <version.wildfly.maven.plugin>1.0.2.Final</version.wildfly.maven.plugin>
        <version.jboss.spec.javaee.7.0>1.0.3.Final</version.jboss.spec.javaee.7.0>
        <version.ejb.plugin>2.3</version.ejb.plugin>

        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.jboss.spec</groupId>
                <artifactId>jboss-javaee-7.0</artifactId>
                <version>${version.jboss.spec.javaee.7.0}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>

        <!-- Import the Common Annotations API (JSR-250), we use provided scope
            as the API is included in WildFly -->
        <dependency>
            <groupId>org.jboss.spec.javax.annotation</groupId>
            <artifactId>jboss-annotations-api_1.2_spec</artifactId>
            <scope>provided</scope>
        </dependency>

        <!-- Import the EJB API, we use provided scope as the API is included in WildFly -->
        <dependency>
            <groupId>org.jboss.spec.javax.ejb</groupId>
            <artifactId>jboss-ejb-api_3.2_spec</artifactId>
            <scope>provided</scope>
        </dependency>

    </dependencies>

    <build>
 
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <!-- WildFly plug-in to deploy the application -->
            <plugin>
                <groupId>org.wildfly.plugins</groupId>
                <artifactId>wildfly-maven-plugin</artifactId>
                <version>${version.wildfly.maven.plugin}</version>
                <configuration>
                    <filename>${project.build.finalName}.jar</filename>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-ejb-plugin</artifactId>
                <version>${version.ejb.plugin}</version>
                <configuration>
                    <ejbVersion>3.1</ejbVersion>
                    <generateClient>true</generateClient>
                </configuration>
            </plugin>

        </plugins>
    </build>

Coding the Client

Our remote EJB client will be a plain Java class. We will create for this purpose a new project, which contains into its pom.xml a dependency to our EJB interfaces. So, start by creating a new Maven project that barely contains a src/java folder. Execute the following command:

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false -DgroupId=com.sample.client -DartifactId=javaee7-ejb-
client-basic

We will add only one class to the project which will invoke remotely our EJBs:

package com.sample.client;

import javax.naming.*;

import com.sample.ejb.SampleBeanRemote;
import com.sample.ejb.SampleBeanRemoteImpl;

import java.util.*;


public class RemoteEJBClient {

    public static void main(String[] args) throws Exception {
        testRemoteEJB();

    }

    private static void testRemoteEJB() throws NamingException {

        final SampleBeanRemote ejb = lookupRemoteEJB();
        String s = ejb.echo("Frank");
        System.out.println(s);
    }

    private static SampleBeanRemote lookupRemoteEJB() throws NamingException {
        final Hashtable jndiProperties = new Hashtable();
        jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");

        final Context context = new InitialContext(jndiProperties);


        final String appName = "";
        final String moduleName = "ejb-remote-server";
        final String distinctName = "";
        final String beanName = SampleBeanRemoteImpl.class.getSimpleName();

        final String viewClassName = SampleBeanRemote.class.getName();
        System.out.println("Looking EJB via JNDI ");
        System.out.println("ejb:" + appName + "/" + moduleName + "/" + distinctName + "/" + beanName + "!" + viewClassName);

        return (SampleBeanRemote) context.lookup("ejb:" + appName + "/" + moduleName + "/" + distinctName + "/" + beanName + "!" + viewClassName);


    }

}

As you can see, the trickiest part of it, is the new JNDI binding where the EJB is looked up. The actual JNDI name depends on the package name where the EJB is shipped. In our case, the server application is named ejb-remote-server.jar, therefore the module name is "ejb-remote-server". If we packed the web module in an ear file we should define the variable appName with the Ear name. 

In order to run this application you need to place a file named jboss-ejb-client.properties in the client classpath:

remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED=false

remote.connections=default

remote.connection.default.host=localhost
remote.connection.default.port = 8080
remote.connection.default.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false

Basically this file contains the host and port where the EJB will be looked up (in our case localhost and the Undertow port which tunnels the request to the EJB Container, in this example, 8080). In terms of configuration.

In order to compile and run the Client project, you will need the following Maven dependencies:

  <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <version.jboss.spec.javaee.7.0>1.0.3.Final</version.jboss.spec.javaee.7.0>
        <version.wildfly.maven.plugin>1.0.2.Final</version.wildfly.maven.plugin>

        <version.exec.plugin>1.2.1</version.exec.plugin>
        <version.war.plugin>3.0.0</version.war.plugin>

        <!-- maven-compiler-plugin -->
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
    </properties>

    <dependencyManagement>
      <dependencies>

         <dependency>
            <groupId>org.jboss.spec</groupId>
            <artifactId>jboss-javaee-7.0</artifactId>
            <version>${version.jboss.spec.javaee.7.0}</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>

         <dependency>
             <groupId>org.wildfly</groupId>
             <artifactId>wildfly-ejb-client-bom</artifactId>
             <version>${version.wildfly}</version>
             <type>pom</type>
             <scope>import</scope>
         </dependency>
      </dependencies>

    </dependencyManagement>

    <dependencies>

        <dependency>
            <groupId>org.jboss.spec.javax.transaction</groupId>
            <artifactId>jboss-transaction-api_1.2_spec</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.jboss.spec.javax.ejb</groupId>
            <artifactId>jboss-ejb-api_3.2_spec</artifactId>
            <scope>runtime</scope>
        </dependency>

       <dependency>
          <groupId>org.wildfly.quickstarts</groupId>
          <artifactId>wildfly-ejb-remote-server-side</artifactId>
          <type>ejb-client</type>
         <version>${project.version}</version>
       </dependency>

        <dependency>
            <groupId>org.wildfly</groupId>
            <artifactId>wildfly-ejb-client-bom</artifactId>
            <type>pom</type>
            <scope>runtime</scope>
        </dependency>

    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>

            <!-- Add the maven exec plug-in to allow us to run a java program
                via maven -->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>${version.exec.plugin}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <executable>java</executable>
                    <workingDirectory>${project.build.directory}/exec-working-directory</workingDirectory>
                    <arguments>
                        <!-- automatically creates the classpath using all
                            project dependencies, also adding the project build directory -->
                        <argument>-classpath</argument>
                        <classpath>
                        </classpath>
                        <argument>org.jboss.as.quickstarts.ejb.remote.client.RemoteEJBClient</argument>
                    </arguments>
                </configuration>
            </plugin>

            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifest>
                            <mainClass>org.jboss.as.quickstarts.ejb.remote.client.RemoteEJBClient</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.wildfly.plugins</groupId>
                <artifactId>wildfly-maven-plugin</artifactId>
                <version>${version.wildfly.maven.plugin}</version>
                <inherited>true</inherited>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>

</build>

As you can see, besides the EJB and Transaction API, we had to reference the Server project (ejb.remote.server) in order to use the Interfaces of our EJB.

Source code for this project is available at: https://github.com/fmarchioni/mastertheboss/tree/master/ejb/remote-ejb

EJB 3 clients from remote hosts

if your EJB 3 clients are arriving from a remote host, you will need to authenticate with an application user in order to be allowed by the remoting protocol.

At first add an user using the add-user.sh script:

[francesco@localhost bin]$ ./add-user.sh 

What type of user do you wish to add? 
 a) Management User (mgmt-users.properties) 
 b) Application User (application-users.properties)
(a): b

Enter the details of the new user to add.
Using realm 'ApplicationRealm' as discovered from the existing property files.
Username : ejbuser
Password : 
Re-enter Password : 
What groups do you want this user to belong to? (Please enter a comma separated list, or leave blank for none)[  ]: 
About to add user 'ejbuser' for realm 'ApplicationRealm'
Is this correct yes/no? yes
Added user 'ejbuser' to file '/home/francesco/jboss/wildfly-10.0.0.Final/standalone/configuration/application-users.properties'
Added user 'ejbuser' to file '/home/francesco/jboss/wildfly-10.0.0.Final/domain/configuration/application-users.properties'
Added user 'ejbuser' with groups  to file '/home/francesco/jboss/wildfly-10.0.0.Final/standalone/configuration/application-roles.properties'
Added user 'ejbuser' with groups  to file '/home/francesco/jboss/wildfly-10.0.0.Final/domain/configuration/application-roles.properties' 

Next, add the user information into your jboss-ejb-client.properties

remote.connection.default.username=ejbuser
remote.connection.default.password=ejbuser123

JBoss AS 7 and EAP 6 Projects

If you are running an older version of the application server, the most relevant change (besides the dependencies) is the jboss-ejb-client.properties which uses the older Remoting Port to allow EJB request, enter the Container:

endpoint.name=client-endpoint
remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED=false
remote.connections=default
 
remote.connection.default.host=localhost
remote.connection.default.port = 4447
remote.connection.default.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false

 

Related articles available on mastertheboss.com

JBoss AS 7 introduction

This is an introduction tutorial to the newest JBoss AS 7 which a

Develop Java EE applications with JBoss AS 7

In this tutorial we will learn how to create and deploy a Java EE

Adding users with JBoss AS 7

If you are planning to add new users to your management interface

Using JBoss AS 7 management API programmatically

In this tutorial we will show how to use detyped management API t

JNDI view in JBoss AS 7

JNDI can be checked on the naming subsystem of the application se

How to install a module on WildFly / JBoss AS 7

Installing a module on WildFly / JBoss AS 7 requires creating a p

Follow us on Twitter