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:
@Inject
into a @FacesConverter
. Use Seam 3 Faces to work around this. UPDATE: This should be fixed in JSF 2.1.@Inject
into a JPA 2.0 EntityListener
or 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-ref
resource names in web.xml
to a global resource name defined in glasfish-web.xml
or jboss-web.xml
doesn'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 glassfish-web.xml
and 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 persistence.xml
as 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.
What a great post!!!
ReplyDeleteI think this should be brought to the attention of Java EE (7) expert group immediately. http://www.jcp.org/en/jsr/detail?id=342 (Not sure if you have done it already).
Thank you very much for this. It's great to see Java EE 6 adoption.
@DWuysan: Thanks. Linda DeMichiel is leading the EE 7 spec, and is also on the JPA 2.1 spec team. I've been in touch with her re the issues with injection into EntityListener and today re persistence.xml's issues with resource-ref. I included a link to this page, so there isn't much more to do.
ReplyDeleteAs for adoption: Well, I'd say I'd "adopted" EE 6 only in the loosest sense. A few days ago I was on the verge of throwing in the towel, calling EE 6 unusably buggy and letting somebody else play beta tester until it worked. That was during a particularly bad patch, though. I know all frameworks have their fair share of warts and horrors, so moving to something else won't magically fix everything.
I suspect that the reason many of these issues exist is because EE 6 started life as a spec, not a real-world product. There wasn't any plan for revising the spec after real-world issues started appearing - and there will always be some. Effort has moved on to EE 7 instead, which will fix some issues and no doubt add a bunch of new ones.
I'd really love to see an EE 6.1 produced and used as a base for EE 7. EE 6.1 would, here, be an EE6 with the holes papered over and the legacy stuff and duplication removed.
For EE 7, very little would make me happier than an "EE7 Legacy Free profile" that completely dropped all support for EJB2, Faces injection, javax.annotation.ManagedBean, etc.
Good news. I've had some comments from folks involved in the ongoing development of EE 7 and JPA 2.1, so I've reached the people I was hoping to reach and raised some awareness of these issues. Time well spent.
ReplyDeleteThanks very much for anyone who took the time to read all this.
Note: Based on feedback received I've fixed the section about EJB3.1 annotations to make it clearer that I'm referring to how they relate to the CDI scope annotations.
Very good post! Overal Java EE 6 is really great, and both GlassFish (3.1.2) and JBoss AS (7.1.1) are now very stable, but the points you address are really important ones.
ReplyDeleteAs for the "You can't @Inject into a @FacesConverter.", this has been addressed in JSF 2.2 (Java EE 7). See http://jdevelopment.nl/jsf-22/#763
There are also strong indications that JSF will drop its native dependency injection in support of CDI. Small signs are that all new integration tests for Mojarra are already using CDI based backing beans.
EJB is indeed a different component model than CDI is. CDI's original purpose was to integrate this model better into the rest of the Java EE platform, so this is no surprise. The next step will be to retrofit EJB as a set services that can be expressed via CDI based annotations. This is already starting, see for instance the public EJB JIRA: http://java.net/jira/browse/EJB_SPEC#selectedTab=com.atlassian.jira.plugin.system.project%3Apopularissues-panel
There are many issues there concerning with "Decoupling service XYZ", with is all for the intend of making it possible to add them to CDI managed beans.
The JAX-RS guys have stated at multiple occasions that they really regret having the @Context stuff and never would have done that if CDI had been available just a tad earlier. If I'm not mistaken this too will be addressed in JAX-RS 2.0.
Btw, did you consider creating any JIRA issues for your concerns?
henk53: I'm glad you found the post useful.
ReplyDeleteI know about @FacesConverter injection, and I'm glad to see the situation improving. CDI injection into @EntityListener is coming in JPA 2.1, which will be another big injection usability fix.
JSF deprecated its own injection support pretty much the day it came out. Because CDI isn't mandatory they couldn't just re-implement them using CDI and mark them @Deprecated like they would ideally have done, though. Yet another case where Java EE's slow-moving, spec-before-real-world approach has caused duplication and confusion IMO.
It's good to hear that the JAX-RS team is aware of the issues with its injection and how it interacts with CDI. Frankly, it's a shame the adoption of CDI into the Java EE spec didn't lead to a month or two delay in the approval of Java EE to permit the cleaner integration of CDI. It would've made for a much less messy spec.
What makes me angry is the lack of a Java EE 6.1 . It's been way too long, and Oracle are much more interested in pushing out JavaEE 7 with cloud-buzzword-compatiblity than they are in fixing Java EE 6.1 to be a usable real-world platform. This post is over 9 months old and the issues it covers were already old when I wrote it.
As for JIRA: I filed issues for some things where there was a clear bug or obvious defect. In other areas the issues were known and understood, but weren't going to get fixed - like all the injection duplication - so there was little point filing issues.
In some cases it was hard to tell where I should file issues, especially at the time I wrote this post. Frequently there were closed expert group mailing lists that I couldn't post on and often couldn't even see history or membership info for so I couldn't ask. Sometimes the spec lead's email address was on the JSR, but I only had responses from a few of them. As I didn't yet understand the way EE was developed very well it was hard to find the JIRA for the specs rather than specific implementations.
I've had success in pushing for a few changes by explicitly contacting the EG working groups and advocating a fix. Injection into @EntityListener is the main one I'm happy to have pushed through. None of those improvements have actually arrived in released specs yet, though, and there's no sign they'll be adopted into a revision of the EE umbrella standard before EE 7, which will surely also introduce a variety of new and exciting defects and bugs.
I'm increasingly very sick of the incredibly slow pace that EE moves. If the standards were very high quality, well-thought out specs and very well documented I could understand it. As it is, EE 6 was a buggy and incomplete spec full of internal inconsistencies that hasn't seen a much-needed revision yet, despite so long having past since it's release. I don't get it.
IMO about the only thing saving Java EE at this point is JBoss AS 7, which actually works most of the time.
Just a quick "Thanks"! The persistence section especially interesting. I'm adapting a JBoss app to run under Glassfish. Yep...
ReplyDeleteWow I should've read this much sooner and saved myself a great deal of development time. I've probably come across all the problems mentioned in this post. Thanks to Java EE 6 now I'm constantly checking out their issues trackers.
ReplyDeleteThis post reflects my experience over the last month and perhaps longer. As a novice, it is difficult to identify a bug I've created or one that exists in the 'pluming'. The documentation is fractured and poorly organized (where's the list of all possible CDI injection possibilities?). Thanks for the summary.
ReplyDelete