Java EE 6 has the potential to be a great platform. It just needs plenty of cleanup, implementation bug fixing, and real-world use to get there.
One of the bigger issues is the significant array of spec inconsistencies and oversights that leave parts of the system working (or not working) in ways very different to one might expect. These issues can greatly increase debugging time and take important time away from productive development and into chasing issues in appservers, frameworks, and the specifications themselves.
I thought I'd make a list of some of the ones I've hit so far. I'm really hoping we'll see a Java EE 6.1 - a fixes-only revision that, unlike EE 7, focuses on polish and usability over adding new features. These are things I think are important to see covered by it.
The shortlist, explained in detail below:
@FacesConverter. Use Seam 3 Faces to work around this. UPDATE: This should be fixed in JSF 2.1.
@Injectinto a JPA 2.0
EntityListeneror an entity. Do a direct BeanManager lookup from JNDI or use Seam 3 Solder's BeanManagerAware to kind of hack around this; it's ugly. Lack of JPA 2.0 lifecycle hooks for EntityListener classes means Seam 3 Persistence can't fix this one. UPDATE Jan 2012: It appears that into EntityListener will be supported in JPA 2.1, but not injection into entities.
javax.annotation.ManagedBean. It was obsolete before release, is only inconsistently recognised, duplicates JSF2 functionality that's also obsoleted by CDI's
@Named, and should simply be removed.
resource-refresource names in
web.xmlto a global resource name defined in
jboss-web.xmldoesn't work for persistence.xml jta datasource references and isn't supposed to work. Ignore all the examples and official documentation that suggest it should, they work with direct JDBC and with Spring but not with JPA 2.0.
beans.xml. Your container will not warn you about this at deployment or runtime even if CDI annotations are present on classes.
Many of these workarounds require the use of Seam 3 (ie: "let's finish the work EE 6 started") modules. Because of Glassfish bugs, some additional work is required to use Seam 3 on Glassfish versions prior to the yet-to-be-released 3.2. See this previous post for some information.
In addition to the genuine issues with EE 6, there's also the learning curve to contend with. A while ago I wrote a broad conceptual overview of EE 6 intended for those coming to it cold, rather than migrating from EE 5 or from Seam. I felt it was necessary because of the struggle I had getting started with EE 6 and the bugs that bit me and confused me during the learning process. Perhaps it'll benefit others here or even provide insight into areas of the documentation and tutorial that would benefit from improvement.
Now, for the details:
CDI vs legacy
The Contexts and Dependency Injection (CDI, JSR299) spec was integrated into Java EE 6 quite late in the process. Most of the other specs were developed concurrently with little or no awareness of CDI and of how significant it'd be in Java EE 6. This shows in several areas that really impact usability and learning time:
- JavaServer Faces 2 includes its own dependency injection framework, introduced in Java EE 6 alongside CDI. This uses the javax.faces.bean.ManagedBean annotation and the javax.faces.bean scope annotations @RequestScoped, @SessionScoped and @ApplicationScoped. These directly duplicate functionality in CDI but only work within JavaServer Faces, creating a fair bit of confusion.
- CDI doesn't work everywhere, and fails silently where unavailable. Because it was introduced quite late, there are places like JPA 2.0 EntityListener classes, JSF 2.0 @FacesConverter classes, etc where injection is not performed. Because CDI never sees these classes, injection fails silently with no error message or other indication so the confused programmer just gets NullPointerExceptions on some injection sites. Needless to say, this doesn't aid learning Java EE 6. It wastes a lot of development time with finding workarounds and additional maintenance, too.
- In areas where CDI is unavailable there is often no good workaround for obtaining access to application resources like (say) an @ApplicationScoped pool of converters for some resource. Some libraries, like JSF2, have lifecycle hooks that permit partial injection support - for example, Seam 3 Faces adds injection but not @PostConstruct support for @FacesConverter. For some other libraries, like JPA 2.0 implementations, there aren't any hooks to use, so user classes have to do their own direct bean lookups using BeanManager. Getting a reference to the BeanManager requires a use of an add-on library like Seam 3 Solder or lots of messing with JNDI lookups. Using the BeanManager once you have a reference to it is messy, poorly documented and inefficient. Seam 3 Solder's BeanManagerAware class (hack) helps a little, but is still a very ugly approach.
- EJB 3.1 EJBs have their own annotations that look similar to CDIs but have very different semantics. The annotations @Singleton, @Stateful and @Stateless declare EJBs, which have container-managed transactions and locking, interface remoting, and more. They also have subtly different lifecycle and scope rules to those for CDI managed beans (@ApplicationScoped, @SessionScoped and @RequestScoped). The EJB vs CDI managed bean differences are quite difficult for the newcomer to understand, especially given the apparent paucity of side-by-side comparision documentation covering both CDI and EJB 3.1 together.
- There are two copies of the ManagedBean annotation, because of an effort to "standardize" bean annotations that occurred parallel to CDI. The javax.annotation.ManagedBean copy is confusing, only works in some of the places where the javax.faces.bean copy does, and should never have been included in the JDK in the first place because it was already obsoleted by cdi by the time of release.
How should these issues be solved?
- Move javax.faces.bean and javax.annotation.ManagedBean into a separate "JavaEE6-legacy" spec jar, and flag them @Deprecated. New projects should not see these annotations at all.
- Make CDI work almost everywhere, and make the exceptions explicit. This requires spec and implementation changes in some places to define lifecycle hooks and hookable factories for helpers like FacesConverters and JPA 2.0 EntityListeners.
- Where CDI isn't available for performance or other reasons, ensure that the CDI implementation is required to report a warning or error at deployment time if annotation scanning finds CDI annotations in classes where they cannot be effective. For example, an @Entity class annotated @RequestScoped or containing @Inject fields should be an error at deployment time. It won't always be possible to detect, but would be a good start.
- Provide a civilized way to access and use the BeanManager from code that still can't get it any other way, via a standard BeanManagerLocator helper class and helper methods in BeanManager for common bean lookup tasks. Legacy code isn't going to go away in a hurry, and not all code can be written to run only within a container; some of it has to work outside a managed container environment too. CDI's purist approach has hurt usability here.
- Make the mixing of CDI and EJB annotations an error at annotation processing time during deployment.
JSF2 and JAX-RS have incompatible access methods for HttpServletRequest etc
Another area where CDI's late introduction shows is in the completely different approach JAX-RS takes to injection. JAX-RS uses method argument injection, not instance variable injection. If you want access to (eg) HttpServletRequest in JAX-RS, you add a method parameter of type HttpServletRequest that's annotated @Context. That's all well and good if you're only writing JAX-RS code. But what if you want to write a class that's callable from JAX-RS and from JSF2 pages?
You have to write two different versions of each method. One that uses JAX-RS method argument injection to get the HttpServletRequest (or whatever else you need) and another that uses FacesContext to obtain it via getExternalContext().
You can't just
@Inject HttpServletRequest req; as a member variable, because CDI injection doesn't know how to inject
HttpServletRequest. You have to use framework-specific methods to obtain it.
Thankfully, the Seam 3 Servlet library largely resolves this issue by introducing a servlet filter that captures HttpServletRequest and stores it in a threadlocal exposed to injection via a CDI producer. This should become part of Java EE 6.1.
JNDI naming is confusing and hard to debug
Despite ongoing efforts into EJB 3.1 and Java EE 6, JNDI naming shows its messy app-server specific history strongly. Different app servers have different naming schemes. It's hard to get a clear picture from within an app of what the JNDI name tree contains from the app's perspective - which, with Java EE 6's resource-ref mapping and module/component scopes is now different to the app server's perspective.
A few changes would really help here:
- Require everything within an app to perform lookups up the component->module->app->global scope hierarchy. Right now there are some exciting exceptions (like
persistence.xml) that make the java ee 6 scopes much less useful, and much much more confusing, than they should be.
- Define some static helper methods on JNDI contexts that recursively dumped a context to an outstream. Yes, they're fairly easy to write, but would be rather helpful for people trying to get an initial handle on issues, especially those who started with Java EE 6 who consequently have little direct contact with or experience with JNDI.
- Define portable global JNDI naming for all resource types, not just EJB 3.1s, so app servers are guaranteed to offer a consistent name for a given resource in a given place.
Related to the above: persistence.xml ignores resource-ref mappings
An important specific case of JNDI issues is with persistence.xml. Different containers like to name resources differently and different deployments have different needs. That's why web.xml has the resource-ref element for declaring an app-internal name for a resource that must be defined, and why container-specific deployment descriptors have elements for mapping referenced resources to global jndi names in the container.
Unfortunately, persistence.xml cannot use those mappings. The persistence context is initialized outside module/component scope and must use global mappings. At least, that's what the Glassfish folks say:
The resource-ref element that you refer to above defines an indirection within a component name space. A PersistenceUnit (EMF) is not initialized within a component and hence always uses the global name space to look up data sources.
Again, we have a case where a useful feature in Java EE 6 only works in some places, some of the time. Again, I wasted hours and hours wondering what I was doing wrong, writing test cases and filing bugs before being told that, actually, it's not supposed to work.
What does work - in glassfish - is defining an app-local data source within web.xml using the <data-source/> element. This is, unlike defining data sources in
jboss-web.xml, theoretically portable. For an example of a datasource defined this way, see this article. Define the datasource in the
java:app/env namespace, eg as
java:app/env/your-ds-name, and reference it from
java:app/env/your-ds-name. It works for me on Glassfish 3.2 build 09 ... but of course that means it has to trigger an apparent bug in JBoss AS 7.
Configuration and deployment descriptors are within the application archive
In Java EE 6, application configuration lives within the application .war / .ear, in xml deployment descriptors or sometimes even in annotations on the Java code its self. That's great for quick and easy testing, but severely limiting for production.
You have to unpack and edit a war to change deployment-specific details like:
- principal-to-role/user mappings in glassfish-web.xml;
- resource-ref mappings in (glassfish|jboss)-web.xml;
- datasources defined in web.xml or in code using Java EE 6 standard annotations;
- datasources, jms resources, javamail sessions, etc defined in glassfish-resources.xml;
- ... and more.
This makes it nearly impossible to distribute a signed war for any non-trivial app using standard EE 6 configuration mechanisms, because the app must be unpacked and edited before deployment. It's also a maintenance and support nightmare. Even better, it requires every user to maintain config patches and merge them whenever upstream code changes.
Currently, there is no standard deployment-time mechanism for overriding bundled settings in web.xml, [container]-resources.xml, [container]-web.xml, etc with deployment specifics like local group names, local database hosts and ports, etc. A mechanism like this would greatly improve the utility of concepts like resource-ref based indirection and principal-to-role mappings.
Containers should also allow web admin console based management of things like role mappings post-deployment.
Glassfish 3.0 was released prematurely, and has buggy CDI until 3.1.1
Glassfish 3.0 had a sufficient number of major CDI bugs that it was practically unusable with Java EE 6 using CDI. Even Glassfish 3.1 needs a manual update of
weld-osgi-bundle.jar to weld 1.1.1 be usable with Seam 3 Solder, Seam 3 Faces, etc, the use of which is necessary to paper over some of the nastier holes in the EE 6 specs.
See my previous posts for lists of CDI bugs in Glassfish, etc.
While not a Java EE 6 spec issue as such, the quality problems with Glasfish early on were a major problem for me and I suspect they caused a significant number of interested people to simply drop the platform and move elsewhere. I persevered, kept reporting bugs, and kept on chasing beta releases, but I've wasted weeks of development time chasing container and platform bugs that I should've spent on my application code. Most people can't afford to do that.