J2EE: Building Serious Business Applications in Java
J2EE: Building Serious Business Applications in Java
Java 2 Enterprise Edition — J2EE — was released in 1999. It was Sun's answer to the question: how do enterprise Java applications handle transactions, security, messaging, and component lifecycle without every team reinventing those wheels?
By 2001 I was leading a team building a J2EE application on JBoss. The platform delivered on parts of its promise and spectacularly failed on others.
What J2EE Provided
J2EE was not a single library. It was a specification: a set of APIs that any compliant application server must implement. The major components:
Servlet and JSP — web tier, request handling, server-side HTML generation. These aged well and are still in use.
Enterprise JavaBeans (EJB) — business components managed by the container. The container handled transactions, security, and remote access. EJBs came in three flavours: session beans (business logic), entity beans (persistence), and message-driven beans (asynchronous processing).
JNDI — the Java Naming and Directory Interface. Resources — database connections, JMS queues, EJB references — were looked up by name from a registry rather than constructed directly.
JTA — Java Transaction API. Distributed transactions spanning multiple resources (database + message queue) managed by the container.
JMS — Java Message Service. Asynchronous messaging with queues and topics, decoupled from the underlying message broker.
A Session Bean
import javax.ejb.*;
public class DeviceServiceBean implements SessionBean {
private SessionContext ctx;
// Container calls this; we look up dependencies from JNDI
public void setSessionContext(SessionContext ctx) {
this.ctx = ctx;
}
// Business method
public DeviceStatus getStatus(String ip) throws RemoteException {
try {
// Look up DAO from JNDI — configured in deployment descriptor
DataSource ds = (DataSource)
new InitialContext().lookup("java:comp/env/jdbc/NmsDS");
// ... query database ...
return status;
} catch (NamingException e) {
throw new EJBException("JNDI lookup failed", e);
}
}
public void ejbCreate() {}
public void ejbRemove() {}
public void ejbActivate() {}
public void ejbPassivate() {}
}
The container managed transaction boundaries. If getStatus threw a RuntimeException, the container rolled back any open transaction automatically.
The Deployment Descriptor
Every EJB required an XML deployment descriptor describing its behaviour:
<ejb-jar>
<enterprise-beans>
<session>
<ejb-name>DeviceService</ejb-name>
<home>com.motorola.nms.DeviceServiceHome</home>
<remote>com.motorola.nms.DeviceService</remote>
<ejb-class>com.motorola.nms.DeviceServiceBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>DeviceService</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
Every component needed one. Deployment meant assembling EAR files containing JAR files containing class files and XML. Mistakes in the XML produced cryptic errors at deploy time.
What Worked
Transaction management was genuinely useful. Having the container handle BEGIN/COMMIT/ROLLBACK meant business code did not have to. When a method annotated (in J2EE 5+) or configured with Required threw, the container rolled back automatically.
JMS decoupling was also valuable: an alert could be raised by publishing to a queue; multiple consumers could process it without the producer knowing about them. The message survived application restarts.
What Did Not Work
Entity beans were a disaster. CMP (container-managed persistence) was the J2EE way to persist objects, but it was inflexible, had poor performance, and produced SQL you could not control. Every team that tried entity beans seriously eventually replaced them with JDBC or, later, Hibernate.
The complexity tax was severe. A stateless session bean that called a database needed: the bean class, a home interface, a remote interface, a local interface, a local home interface, and two XML descriptors. Eight files for what was essentially a class with a method.
Portability was theoretical. J2EE claimed write-once, run-on-any-server. In practice, JBoss, WebLogic, and WebSphere each had proprietary extensions you needed for anything real. Migrating between them was a project.
Spring (2003) solved the transaction and dependency injection problems without the ceremony. Hibernate solved the persistence problem. J2EE survived by eventually incorporating both ideas — EJB 3.0 (2006) looked a lot like Spring.