In Java Enterprise, the EJB (Enterprise Java Bean) technology is often used to create a service layer of a J2EE application running in an application Server (like Glassfish or Wildfly). While accessing these EJB instance is relatively easy from inside the same application (using @Inject or @EJB annotations for automatic dependency injection), it is sometimes also required to call some methods on the EJBs from outside the application server.
The JavaEE standard provides the “EJB remoting” functionality to do so. In this article I want to show how to access an EJB running in a Wildfly application server from a standalone Java application.
All code examples are also available on our Github page: https://github.com/illucIT/remote-ejb-example
Prerequisites
In this article I will use the JavaEE7 standard, with the following software versions:
Project Layout
To demonstrate the communication between the two processes, we will need two projects. One of them will be running inside the application server (I will call it ejb-remote-server from now on) and the other one will run as Java standalone application (ejb-remote-client).
Server Project
For the project ejb-remote-server you can just create a new maven project. Add the maven EJB plugin and set the project packaging to “ejb”, so it can be deployed to the application server later. For a minimal EJB example, you can import the pom “org.jboss.spec:jboss-javaee-7.0:1.0.0.Final” in your dependency management and then add the dependency “org.jboss.spec.javax.ejb:jboss-ejb-api_3.2_spec” as provided.
Here is an example how your pom.xml could look like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
<?xml version="1.0"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.illucit</groupId> <artifactId>ejb-remote-server</artifactId> <version>1.0.0</version> <packaging>ejb</packaging> <name>WildFly Example: EJB Remote Server</name> <description>Server-Side EJB for remote EJB example on Wildfly</description> <url>http://www.illucit.com</url> <licenses> <license> <name>Apache License, Version 2.0</name> <distribution>repo</distribution> <url>http://www.apache.org/licenses/LICENSE-2.0.html</url> </license> </licenses> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- JBoss dependency versions --> <version.jboss.spec.javaee.7.0>1.0.0.Final</version.jboss.spec.javaee.7.0> <!-- other plugin versions --> <version.compiler.plugin>3.1</version.compiler.plugin> <version.ejb.plugin>2.3</version.ejb.plugin> <!-- maven-compiler-plugin --> <maven.compiler.target>1.7</maven.compiler.target> <maven.compiler.source>1.7</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> <dependency> <groupId>org.jboss.spec.javax.ejb</groupId> <artifactId>jboss-ejb-api_3.2_spec</artifactId> <scope>provided</scope> </dependency> </dependencies> <build> <!-- Set the name of the deployment --> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>${version.compiler.plugin}</version> <configuration> <source>${maven.compiler.source}</source> <target>${maven.compiler.target}</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-ejb-plugin</artifactId> <version>${version.ejb.plugin}</version> <configuration> <ejbVersion>3.1</ejbVersion> </configuration> </plugin> </plugins> </build> </project> |
Now for testing purposes, create a simple EJB with interface and implementation.
Important: Make sure, your EJB has a remote interface, annotated with @Remote . If you also need a local interface for it (e.g. to inject it into other beans via CDI) you should consider adding another ( @Local ) interface view for your EJB.
Remote Interface (src/main/java/com/illucit/ejbremote/server/ExampleService.java):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package com.illucit.ejbremote.server; import java.util.Map; import javax.ejb.Remote; @Remote public interface ExampleService { public String greet(String name); public Map<Object, Object> getSystemProperties(); } |
Implementation (src/main/java/com/illucit/ejbremote/server/ExampleServiceImpl.java):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package com.illucit.ejbremote.server; import java.util.HashMap; import java.util.Map; import javax.ejb.Stateless; @Stateless public class ExampleServiceImpl implements ExampleService { @Override public String greet(String name) { return "Hello " + name + "!"; } @Override public Map<Object, Object> getSystemProperties() { return new HashMap<>(System.getProperties()); } } |
As a next step, we need to define the module name of the deployed project. Per default the application server names the deployment after the provided JAR or WAR file.
In order to configure the module name, you can create a file “ejb-jar.xml” in your META-INF directory to tell the application server the correct module name:
src/main/resources/META-INF/ejb-jar.xml:
1 2 3 4 5 |
<ejb-jar xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/ejb-jar_3_2.xsd" version="3.2"> <module-name>ejb-remote-server</module-name> </ejb-jar> |
Finally you should be able to build your project with mvn clear compile package and deploy the generated JAR file to the application server (e.g. via the management interface).
Client Project
Next we can create the client project. I will also use maven to add the dependencies required for the EJB remoting.
First of all, you have to add the classes (at least the remote interfaces and all linked classes) of your own EJB project to your client project. This can be done by a simple maven dependency with the type “ejb”.
Furthermore the maven artifacts “org.jboss.spec:jboss-javaee-7.0:1.0.0.Final” and “org.wildfly:wildfly-ejb-client-bom:8.1.0.Final” have to be added to the dependency management as imports, so maven knowns the artifact versions. Then you can add the required APIs for the EJB client remoting (see code below). Most of the dependencies can be added as “runtime” depedency, as they are not required during compile time.
Your pom.xml could look like this (Note that the exec-maven-plugin is only used to run the application afterwards via maven):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
<?xml version="1.0"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.illucit</groupId> <artifactId>ejb-remote-client</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <name>WildFly Example: EJB Remote Client</name> <description>Java client for remote EJB example on Wildfly</description> <url>http://www.illucit.com</url> <licenses> <license> <name>Apache License, Version 2.0</name> <distribution>repo</distribution> <url>http://www.apache.org/licenses/LICENSE-2.0.html</url> </license> </licenses> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- JBoss dependency versions --> <version.wildfly>8.1.0.Final</version.wildfly> <version.jboss.spec.javaee.7.0>1.0.0.Final</version.jboss.spec.javaee.7.0> <!-- other plugin versions --> <version.compiler.plugin>3.1</version.compiler.plugin> <version.exec.plugin>1.2.1</version.exec.plugin> <!-- maven-compiler-plugin --> <maven.compiler.target>1.7</maven.compiler.target> <maven.compiler.source>1.7</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> <!-- Business Interfaces of the server EJB. --> <dependency> <groupId>com.illucit</groupId> <artifactId>ejb-remote-server</artifactId> <type>ejb</type> <version>${project.version}</version> </dependency> <!-- Import the transaction spec API, we use runtime scope because we aren't using any direct reference to the spec API in our client code --> <dependency> <groupId>org.jboss.spec.javax.transaction</groupId> <artifactId>jboss-transaction-api_1.2_spec</artifactId> <scope>runtime</scope> </dependency> <!-- Import the EJB 3.1 API, we use runtime scope because we aren't using any direct reference to EJB spec API in our client code --> <dependency> <groupId>org.jboss.spec.javax.ejb</groupId> <artifactId>jboss-ejb-api_3.2_spec</artifactId> <scope>runtime</scope> </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> <!-- client communications with the server use XNIO --> <dependency> <groupId>org.jboss.xnio</groupId> <artifactId>xnio-api</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.jboss.xnio</groupId> <artifactId>xnio-nio</artifactId> <scope>runtime</scope> </dependency> <!-- The client needs JBoss remoting to access the server --> <dependency> <groupId>org.jboss.remoting</groupId> <artifactId>jboss-remoting</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.jboss</groupId> <artifactId>jboss-remote-naming</artifactId> <scope>runtime</scope> </dependency> <!-- Remote EJB accesses can be secured --> <dependency> <groupId>org.jboss.sasl</groupId> <artifactId>jboss-sasl</artifactId> <scope>runtime</scope> </dependency> <!-- data serialization for invoking remote EJBs --> <dependency> <groupId>org.jboss.marshalling</groupId> <artifactId>jboss-marshalling-river</artifactId> <scope>runtime</scope> </dependency> </dependencies> <build> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>${version.compiler.plugin}</version> <configuration> <source>${maven.compiler.source}</source> <target>${maven.compiler.target}</target> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>${version.exec.plugin}</version> <configuration> <mainClass>com.illucit.ejbremote.EjbRemoteClient</mainClass> </configuration> </plugin> </plugins> </build> </project> |
In order to test the EJB client connection, first create a new class (e.g. src/main/java/com/illucit/ejbremote/EjbRemoteClient.java) with a main-Method. Then you can add the code to create a connection to your EJB and call methods on it.
Step 1: Create EJB naming context
First of all, you need a JNDI context. The context factory is provided in the maven artifact “org.jboss:jboss-remote-naming” and will create a JNDI context that is able to resolve “ejb” URLs into proxies to the remote application server process.
Define connection host and port according to your application server. The port is the same that you use for normal HTTP traffic.
The following function creates a JNDI context for EJB remoting.
With the parameter Context.INITIAL_CONTEXT_FACTORY you define the factory class. With Context.URL_PKG_PREFIXES you can define a package to scan for additional naming context. You need this to parse the ejb: prefix we will use later. The parameter org.jboss.ejb.client.scoped.context = false will tell the context to read the connection parameters (like connection host and port) from the provided map instead of an additional classpath configuration file. This is especially helpful, if you want to create a JAR bundle which should be able to connect to different hosts, so you can’t hard-code it in the classpath. The parameter Context.PROVIDER_URL will define the connection scheme and should start with http-remoting:// .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
private static Context createRemoteEjbContext(String host, String port) throws NamingException { Hashtable<Object, Object> props = new Hashtable<>(); props.put(INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory"); props.put(URL_PKG_PREFIXES, "org.jboss.ejb.client.naming"); props.put("jboss.naming.client.ejb.context", false); props.put("org.jboss.ejb.client.scoped.context", true); props.put("endpoint.name", "client-endpoint"); props.put("remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED", false); props.put("remote.connections", "default"); props.put("remote.connection.default.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS", false); props.put(PROVIDER_URL, "http-remoting://" + host + ":" + port); props.put("remote.connection.default.host", host); props.put("remote.connection.default.port", port); return new InitialContext(props); } |
Step 2: Get EJB URL
In order to connect to your EJB, you will need an URL you can feed into the context. The URL Wildfly recognizes is of the following format:
1 |
ejb:${appName}/${moduleName}/${beanName}!${remoteView} |
- The ${appName} variable is the application name of your deployment. If you used no EAR file but a simple JAR or WAR deployment (like in this example), the application name will be empty
- ${moduleName} is the name we set for our deployment earlier, so it is “ejb-remote-example” (as defined in ejb-jar.xml)
- The ${beanName} variable is the simple name of the implementation class of the EJB, so in our example it is “ExampleServiceImpl”
- ${remoteView} denotes the fully-qualified interface name of the remote interface (here it is “com.illucit.ejbremote.server.ExampleService”)
This constitutes the full EJB URL:
1 |
String ejbUrl = "ejb:/ejb-remote-server/ExampleServiceImpl!com.illucit.ejbremote.server.ExampleService"; |
Step 3: Create EJB proxy
In order to create a proxy for your EJB, you can use the following method (using generics for the remote interface type):
1 2 3 4 5 |
private static <T> T createEjbProxy(Context remotingContext, String ejbUrl, Class<T> ejbInterfaceClass) throws NamingException, ClassCastException { Object resolvedproxy = remotingContext.lookup(ejbUrl); return (T) resolvedproxy; } |
The first parameter is your created JNDI context from Step 1, the ejbUrl is the one from Step 2 and the third parameter is the class of the remote interface of the EJB.
Step 4: Call EJB Methods
As a last step, you can combine your results from steps 1-3 and create an actual proxy of your EJB. The connection is not created instantly, but when you first call a method on the returned proxy the connection will be established.
Please note, that the objects transferred to your EJB as parameters (and return values) are value-types and are serialized in the process. So only send and expect values and don’t send transient objects (like open database connections, streams etc.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
public static void main(String[] args) { // Connection to Wildfly Server instance String host = "127.0.0.1"; String port = "8080"; // Wildfly HTTP port Context remotingContext; try { remotingContext = createRemoteEjbContext(host, port); } catch (NamingException e) { System.err.println("Error setting up remoting context"); e.printStackTrace(); return; } // Syntax: ejb:${appName}/${moduleName}/${beanName}!${remoteView} // appName = name of EAR deployment (or empty for single EJB/WAR // deployments) // moduleName = name of EJB/WAR deployment // beanName = name of the EJB (Simple name of EJB class) // remoteView = fully qualified remote interface class String ejbUrl = "ejb:/ejb-remote-server/ExampleServiceImpl!com.illucit.ejbremote.server.ExampleService"; ExampleService service; try { service = createEjbProxy(remotingContext, ejbUrl, ExampleService.class); } catch (NamingException e) { System.err.println("Error resolving bean"); e.printStackTrace(); return; } catch (ClassCastException e) { System.err.println("Resolved EJB is of wrong type"); e.printStackTrace(); return; } // Call remote method with parameter String toGreet = "World"; String exampleResult; try { exampleResult = service.greet(toGreet); } catch (Exception e) { System.err.println("Error accessing remote bean"); e.printStackTrace(); return; } // Hello World! System.out.println("Example result: " + exampleResult); // Retrieve result from EJB call Map<Object, Object> systemProperties; try { systemProperties = service.getSystemProperties(); } catch (Exception e) { System.err.println("Error accessing remote bean"); e.printStackTrace(); return; } System.out.println("Wildfly Home Dir: " + systemProperties.get("jboss.home.dir")); } |
In order to run your code, you can use the “exec-maven-plugin” shown in the pom.xml. You only need to add the class containing your main-Method as “configuration/mainClass” and call the maven command:
1 |
mvn clean compile exec:java |
hello, i’m new on EJB but not on JSF, JSP and others java technologies, i’m asking myself here, why the hell EJB is so complicated to generate a client? it’s useless if you need the server classes/code to do a client, because you need expose your code, so what’s the point? or EJB is deprecated and is a must-avoid technology nowadays? Thanks in advance!
Hi Rodrigo,
Good question!
I suppose the EJB technology is generally rather heavy-weighted as you always require an Java Application Server (or more precise, an EJB container) to run those enterprise beans, and also you call the methods on your bean reference directly. In contrast, API specifications like REST or SOAP don’t need any imports of interface classes, but instead they use other mechanics (like the WSDL format in SOAP) to specify the communication between the client and the server. A positive aspect of the EJB behaviour is that you don’t need to define any serialization by yourself, as it is completely handled by the EJB architechture (as long as your parameter types are serializable and don’t contain any transient data like open connections etc.)
If you really need to run the EJB container on another machine or in another JVM than the EJB client, you only need the bean interfaces, not the implementation classes, however. So I would recommend that you program your client code against the interface of your EJBs, put those interfaces in another utility project, both imported by the client and the server, and then include the EJB implementation classes only in the server project.
Additionally, I suggest that you use EJB for remote calls only in projects where you control both the server and the client side. If you need to provide a public API of your project for any third parties, I would rather facilitate a RESTful Web API instead of exposing your EJBs.
I hope that was helpful for you!
Best regards,
Christian
I would always use as you said, JAX-WS / JAX-RS and not EJB, because is simpler! however, soon I will need work with EJB due some legacy systems in my company, that’s why I asked you. Thank you a lot for your explanation, and helpful post!
dear sir good after noon i am new in EJB.i have installed glassfish server but when i create new connection factory in JMS resurce glassfish4 server give error class java.lang.RuntimeException or this same error i got when i create new data source so please hemp me how to fix this error
or i have installed wildfly server 9 but i am unable to see JMS option in admin consol in browser
can you give me one simple example of MDB in wildfly server or glassfish server
i am using JDK1.8
Hi,
I probably should have mentioned above that the example is for (stateless) session beans.
In my understanding Message-Driven Beans work a bit differently, as you don’t call the methods of a proxy object directly, but rather send message objects to a message queue. So the connection with the JNDI Context will most likely stay the same, but instead of generating a proxy from the context with the “ejb:” prefix, you need to get the message factory and the queue.
Hi Christian, Thanks for the example, very helpful. I have a query, in above example you have added dependency of server project in child project pom.XML. Is there a ways by which I don’t have add dependency in XML file and at run time I get my server EJB reference while running child class methods.
Thanking you in advance.