I had once a interesting discussion with a Java-developer, who hated EJBs and prefered POJOs in web container. I had to review his architecture and asked him about his solution:
I: How do you solved the transactionality?
Developer: Well, it was very easy. I only implemented a ThreadLocal. I open the transaction in my servlet and store the connection in ThreadLocal. My DAOs take the connection from the ThreadLocal and use it.
I: Cool. How long it took to implement the solution?
Developer: Only 2 days with testing and doc.
I: O.k. Do you have some monitoring requirements?
Developer: Yes we have. I implemented a simple solution with Dynamic Proxy and a factory. I'm able now to monitor the methods. I combined the decorator with Log4J -> and it works well. I spent only one day for the implementation.
I: You need persistence?
Developer: Yes. But I do not like CMP 2.0. Also Hibernate was too complicated. I implemented my own lightweight DAO-solution. I map database records to VOs using reflection. Is much easier, than OR-stuff. I spent only 2 days implementing the whole framework. It is very easy.
I: Is it transactional?
Developer: Of course.
I: What happens if you request 2 times the same object in the same transaction?
Developer: You will get two instances!!
I: So it is not transactional. You should be careful. The whole stuff can become inconsistent...
Developer: Really? No Problem. I can also implement a transactional first level cache - it is only a Map...
We discussed also other issues, like threading, scalability constraints, real time monitoring etc. He provided or implemented for every needed aspect an own solution. I approved finally his design, because it was clean and the developer really experienced. But: the code is clean but not maintainable! Many Java developers do not know what e.g. ThreadLocal is and how it works.
It is nothing special in this conversation. If you consider only the development phase, EJBs seem to be too complicated. If you consider also the non functional stuff, it becomes more interesting. Only few developers care about real time monitoring (JSR-77), threading, proper load balancing etc. If you are using POJOs in production you will only see your servlet and you will not know what your application is doing. So EJBs remain complex, but the complexity has its roots not in the EJB spec, but in the fact, that we are mostly developing distributed and transactional applications.
EJB 2.1 were not elegant, there were some design flaws (e.g. the method getPrimaryKey in EJBContext, can be only invoked in case you are in an CMP 2.0), you had to remember some rules (e.g. remote methods had to throw RemoteExceptions etc.), but it was not complicated. Experienced Java-Developers were able to deploy a CRUD J2EE 1.4 applications in about 0,5 hour (Ant+Xdoclet).
EJB 3.0 are really great. But also not easy. Just see my entry "Nothing Is Transparent".
Both specs J2EE 1.4 and Java EE 5 are o.k., but Java EE 5 is nicer and cleaner...
NEW 2016: Java EE 7 Workshops: Bootstrap, Effective, Architectures, April, 4th-6th, Munich's Airport
On demand workshops: Java EE 7 Bootstrap, Effective Java EE 7 and NEW: Java EE 7 Testing are available for streaming