Trouble With Crippled Java EE 6 APIs in Maven Repository And The Solution

If you try to load the javax.persistence.EntityManager class coming from standard java.net Repository you will get the following Exception:


java.lang.ClassFormatError: Absent Code attribute in method that is not native or abstract in class file javax/persistence/LockModeType
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
        at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:190)


Loading a class is needed for mocking, so the following code will not run:

@Stateless
public class EJB3WithEntityManager {
    @PersistenceContext
    EntityManager em;
    
    public void save(AnEntity ae){
        em.persist(ae);
    }
}

public class EJB3WithEntityManagerTest {
    private EJB3WithEntityManager cut;
    @Before
    public void injectEntityManager(){
        this.cut = new EJB3WithEntityManager();
        this.cut.em = mock(EntityManager.class);
    }
    @Test
    public void testSomeMethod() {
        AnEntity ae = new AnEntity();
        this.cut.save(ae);
        verify(this.cut.em,times(1)).persist(ae);
    }
}

Instead of using
<dependency>
    <groupId>javax</groupId>
    <artifactId>javaee-web-api</artifactId>
    <version>6.0</version>
    <scope>provided</scope>
</dependency>

You should use alternative (geronimo, jboss etc.) dependencies:

<dependency>
       <groupId>org.apache.geronimo.specs</groupId>
       <artifactId>geronimo-ejb_3.1_spec</artifactId>
       <version>1.0</version>
       <scope>provided</scope>
   </dependency>
   <dependency>
       <groupId>org.apache.geronimo.specs</groupId>
       <artifactId>geronimo-jpa_2.0_spec</artifactId>
       <version>1.0</version>
       <scope>provided</scope>
   </dependency>

I cannot imagine any reasonable motivation behind removing the implementation of API-classes before uploading them into central maven repository, except political or licensing issues. This approach, however, has one advantage. You can tell whether a project is actually unit tested, or not, just by looking at the POM :-).
The whole example with workaround was checked into http://kenai.com/projects/javaee-patterns The name of the project is: MavenUnitTestWithCrippledAPI.

Comments:

You don't have to swap the dependencies - it's enought to have the implementations in "test" scope *before* the API dependency:

e.g. Eclipselink for an outside-of-container test of a glassfish project:

<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc5</artifactId>
<version>11.2.0.1.0</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<scope>provided</scope>
<version>6.0</version>
</dependency>

Posted by Matthias Fraass on October 20, 2010 at 03:53 PM CEST #

@Matthias,

but why? They are "provided" anyway. Just going with one set of dependencies makes the POM shorter. javaee-web-api is the WEB-API, and the other dependencies are the API with implementation.

thanks!,

adam

Posted by adam-bien.com on October 20, 2010 at 04:10 PM CEST #

Hi, you also could use different profiles.

The test profile could then use the implementation-jar.

The profile for generating the product could use the api-jar.

Posted by Stefan Bohn on October 20, 2010 at 05:53 PM CEST #

you are right, you can omit that.

Posted by Matthias Fraass on October 20, 2010 at 06:58 PM CEST #

@Stefan,

sure. Instead of using the bogus Java EE 6 class files, I would just use the "real" implementations and not refer to the Java EE 6 API at all.

But in general - you are right :-)
The real problem is the bogus JAR in maven repo. Our ideas are just workarounds :-)

thanks for your thoughts!,

adam

Posted by adam-bien.com on October 20, 2010 at 07:07 PM CEST #

It was my understanding that the reason they are not in the central repo is licensing issues. :(

Posted by jkilgrow on October 20, 2010 at 08:49 PM CEST #

@jkilgrow,

could be. I guess even warranty claims or something like that. The problem are a few abstract classes with removed implementation. They could be implemented from scratch without any implementation in hours if not minutes.

I guess it would solve several millions of man hours worldwide :-)

thanks!,

adam

Posted by adam-bien.com on October 20, 2010 at 11:28 PM CEST #

there is other solution if you are using maven >=2.0.9

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.6.0.CR2</version>

<scope>test</scope>

</dependency>

<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>

more information:

http://stackoverflow.com/questions/2116220/how-can-i-use-different-jars-for-compiling-and-testing-in-maven

Posted by 89.72.75.239 on October 21, 2010 at 12:50 AM CEST #

@89.72.75.239,

(you have a geeky name :-)).
Sure - it is identical to the @Matthias suggestion.
But I would just use Hibernate, without javaee-web-api then.

thanks for the link,

adam

Posted by adam-bien.com on October 21, 2010 at 01:07 AM CEST #

I think it's not licensing nor warranty concerns (that missing code is trivial). I think it's just that it's not part of the spec! The implementers could choose to do some specific stuff in there... but your unit tests should not depend on those.

Instead of removing the code, it would be nicer to have all methods throw an exception instead, wouldn't it?

But I wonder why you're having problems with the EntityManager. That is an interface, so there should be no code block missing and the mocking should work!

Rüdiger

Posted by Rüdiger on October 21, 2010 at 04:14 PM CEST #

Wasn't one of the goals of Gemini to expose Java EE 6 and related artifacts in a better way ?;-)

JSR-330 is another hilarious example.

After PFD-1
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1.0-PFD-1</version>
</dependency>
which caused a whole bunch of online flames especially from EE stakeholders
it ended up as this:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>

Quick and Dirty and a bit love-less I'd say, others in the JCP call that "Agile" ;-|

Posted by Werner Keil on February 06, 2011 at 05:40 PM CET #

Other way is to use:

<dependency>
<groupId>org.glassfish.extras</groupId>
<artifactId>glassfish-embedded-all</artifactId>
<version>3.1</version>
<scope>provided</scope>
</dependency>

with

<repository>
<id>java.net</id>
<name>GlassFish Maven Repository</name>
<url>http://download.java.net/maven/glassfish</url>
</repository>

Then you have all dependencies including javax.validation.

Posted by Przemysław Pelczar on July 18, 2011 at 03:48 PM CEST #

Hi all,

I found a nice workaround on Arquillian wiki (https://community.jboss.org/wiki/WhatsTheCauseOfThisExceptionJavalangClassFormatErrorAbsentCode). It seems to work for me, even with non-Arquillian tests.

<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.9</version>
<configuration>
<classpathDependencyExcludes>
<!-- exclude code absent api -->
<classpathDependencyExclude>javax:javaee-api</classpathDependencyExclude>
<classpathDependencyExclude>javax:javaee-web-api</classpathDependencyExclude>
</classpathDependencyExcludes>
</configuration>
</plugin>

Posted by Alexis on April 03, 2012 at 08:51 AM CEST #

Another ugly thing is that these javax API classes in the maven repository do not come with sources or API. No fun debugging or just hovering ;-)

@Adam: Thx for your always informative blogs!

Posted by Jan Wiemer on April 06, 2012 at 02:37 AM CEST #

@Alexis: thanks, your workaround worked for me beautifully. I am getting a similar error as that posted here but instead related to javax-rpc.

Posted by Brandon on October 13, 2012 at 02:26 AM CEST #

One thing bothers me :)
What does "cut" abbreviation stand for?

Posted by Grzesiek Demecki on April 22, 2014 at 09:20 PM CEST #

@Grzesiek,

bothers you?

Its Class Under Test of course :-)

Cut is also a nice reference to "One Of These Days I will Cut You Into Little Pieces". A nice song to forget the red bar :-)

cheers,

adam

Posted by Adam Bien on April 23, 2014 at 11:05 AM CEST #

By referencing the jboss implementation you are creating a tight coupling between your app and the container it will eventually be deployed in.

The whole point of having only the API available to you is to enable you to create code that will be deployable into any container. Different vendors create their own implementation of the API, which will be provided to the app when it is deployed. When you declare a reference to a specific implementation, you are defeating that purpose entirely. You could run into situations where your tests will succeed but the app will fail when deployed. Not a good thing.

The LockModeType exception to me suggests a classloader issue, which could have any number of causes. Likely causes include having multiple versions of the api in the classpath, possibly pulled in by maven as transient dependencies. You should investigate that.

Posted by Michael on June 11, 2014 at 01:09 PM CEST #

@Michael,

"By referencing the jboss implementation you are creating a tight coupling between your app and the container it will eventually be deployed in."

The app only relies on the Java EE APIs and not the server implementation.

"The whole point of having only the API available to you is to enable you to create code that will be deployable into any container. Different vendors create their own implementation of the API, which will be provided to the app when it is deployed. When you declare a reference to a specific implementation, you are defeating that purpose entirely. You could run into situations where your tests will succeed but the app will fail when deployed. Not a good thing."

++1
Again-> your application only becomes dependent, if you import the application server stuff.

"The LockModeType exception to me suggests a classloader issue, which could have any number of causes. Likely causes include having multiple versions of the api in the classpath, possibly pulled in by maven as transient dependencies. You should investigate that."

The issue is clear: Sun / Oracle removed the implementation from the APIs and made them unload able. However: it was fixed in Java EE 7: http://www.adam-bien.com/roller/abien/entry/javaee_7_heals_crippled_jars

thanks for you comment!,

adam

Posted by Adam Bien on June 12, 2014 at 02:38 AM CEST #

"I cannot imagine any reasonable motivation behind removing the implementation of API-classes before uploading them into central maven repository"
It's working as intended, you're just messing concepts...
API stands for "Application Programming Interface" this means it's just intended to be the mock classes. The whole API is an amalgamation of modules, each one having it's own reference implementation.
You're getting the error just because you're trying to execute some code with that API, that's always intended to be used in provided scope, and it'll be applications intended to be run in an EE environment (application server) where all those classes are present with a proper implementation.
For tests or executions you should add each JSR as a dependency from whichever implementation you want (i.e. for persistence API you can use Hibernate, EclipseLink or OpenJPA) and then each server would have its own, but that doesn't mean the jar is wrong. The full bundle RI is GlassFish (https://glassfish.java.net/downloads/ri/)
Anyway it's true that the jar has some flaws because it overrides Java SE classes that will be properly implemented in any running VM (notice SOAP).

Posted by Yago on September 26, 2014 at 08:50 AM CEST #

Kenai is no more. Would you care to upload the code to Github? Thanks.

Posted by Luca on July 31, 2019 at 07:28 PM CEST #

Post a Comment:
  • HTML Syntax: NOT allowed
...the last 150 posts
...the last 10 comments
License