Helidon, the Java microframework I work on at Oracle for my day job, just recently crossed the 1,000 star mark on Github. It’s a dopey metric in many regards, sure, but it’s nice to see people like it!
When you have a Maven project that you want to be runtime-independent, but coded to a particular JCP specification, things get a little hairy. Your code must compile against an API jar (or API jars, or, if you’ve read this series of posts, a specification), but you want it to run at test time against a particular environment, and you want that environment to dictate which API jars should be used.
This sounds like a contradiction until we realize that Maven respects the order in which
<dependency> elements are listed.
The TL;DR here is: when you are putting together your
<dependency> elements, list them in your
pom.xml in typically reverse order: list
test-scoped dependencies first, then
runtime-scoped dependencies, then
provided-scoped dependencies and finally
When you compile, none of the
test-scoped dependencies will be “in scope”, so they will simply be ignored. Next on the classpath will be your
provided-scoped dependencies followed by your
compile-scoped dependencies. At compile time, none of this really matters: where your
provided-scoped dependencies appear on your classpath is typically irrelevant.
But when you test, the order is suddenly quite important. If your
test-scoped dependencies come first, then any transitive API jars that they pull in (for example) will be the ones that are actually used on your classpath—they’ll “come before” any
provided-scoped API jars you have in order to support runtime environment-independent compilation. Those
provided-scoped dependencies will still be on your classpath at test time, but they’ll never be referenced: the runtime environment you’re testing against should (must!) dictate its own API jars to be used, so those are the ones you’ll end up using.
To make this slightly more concrete, let’s say you have a CDI project and you want to be able to run it on either Weld or OpenWebBeans. Let’s say further that you’re going to run unit tests using Weld. Now recall that Weld uses the JBoss-authored version of
javax.interceptor packages reified in the
org.jboss.spec.javax.interceptor:jboss-interceptors-api_1.2_spec:jar artifact, not the
javax.interceptor:javax.interceptor-api:jar artifact. To preserve runtime environment independence, you want to compile (let’s say) against the
javax.interceptor:javax.interceptor-api:jar artifact, but when you test with Weld you want Weld to use whatever API jars it wants, not the runtime-environment-independent ones you happen to have selected and compiled against.
To do this, you’ll add Weld first in
test scope, then
provided scope. When you compile, you’ll use
javax.interceptor:javax.interceptor-api:jar; when you test you’ll end up transitively using
When you expand this strategy to include environments and specifications as defined earlier in this series of posts, component-oriented development becomes much simpler.