Lombok: Issue with in memory class compilation during unit test using h2 database

Created on 24 Mar 2016  路  10Comments  路  Source: projectlombok/lombok

Hi lombok team,

First I'd like to thank you for the amazing work that you have done. This project is awesome and make java great again.

Second, I'd like to ask you about an issue which annoys me for a couple of weeks. I've been trying to resolve it myself but couldn't with my limited knowledge.

Environment:
JDK: 1.8.0_60
lombok: 1.16.8 (scope: provided)
Spring Boot: 1.3.0.M5

We are using h2 database (in-memory, Oracle mode) for unit testing. Since H2 is not currently support TO_DATE method which Oracle has (and we use it). We define the below in one of our setup script:

CREATE ALIAS TO_DATE as '
import java.text.*;
@CODE
java.util.Date toDate(String s, String dateFormat) throws Exception {
  return new SimpleDateFormat(dateFormat).parse(s);
}
';

Once unit test runs, it throws exception as below:

Caused by: org.h2.jdbc.JdbcSQLException: Syntax error in SQL statement "/org/h2/dynamic/TO_DATE.java:4: warning: Can't initialize javac processor due to (most likely) a class loader problem: java.lang.NoClassDefFoundError: com/sun/tools/javac/processing/JavacProcessingEnvironment
public class TO_DATE {
       ^
    at lombok.javac.apt.LombokProcessor.init(LombokProcessor.java:84)
    at lombok.core.AnnotationProcessor$JavacDescriptor.want(AnnotationProcessor.java:87)
    at lombok.core.AnnotationProcessor.init(AnnotationProcessor.java:141)
    at lombok.launch.AnnotationProcessorHider$AnnotationProcessor.init(AnnotationProcessor.java:53)
    at com.sun.tools.javac.processing.JavacProcessingEnvironment$ProcessorState.<init>(JavacProcessingEnvironment.java:500)
    at com.sun.tools.javac.processing.JavacProcessingEnvironment$DiscoveredProcessors$ProcessorStateIterator.next(JavacProcessingEnvironment.java:597)
    ....<more omitted>
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180)
    at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:283)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:173)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:153)
    at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:128)
    at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:203)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:155)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:103)
  Caused by: java.lang.ClassNotFoundException: com.sun.tools.javac.processing.JavacProcessingEnvironment
    at java.lang.ClassLoader.findClass(ClassLoader.java:530)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at lombok.launch.ShadowClassLoader.loadClass(ShadowClassLoader.java:418)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 113 more

It looks like by using lombok in our project, it provides a different compiler or so which is unable to compile this class and fails our test cases.

If we remove lombok dependency and don't use any of the lombok annotations, it's working fine.

Looking forward to hearing your response on this. Any help would be very appreciated since we really love to use lombok and don't want to remove it just because of this.

If you need additional information, please let me know.

Thanks a lot!
Vu

Most helpful comment

Meanwhile, this can be fixed using surefire to exclude lombok from the test classpath:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <classpathDependencyExcludes>org.projectlombok:lombok</classpathDependencyExcludes>
    </configuration>
</plugin>

All 10 comments

Have you found a workaround in the mean time? We're running into this exact same issue.

Unfortunately no. We couldn't adopt lombok due to this problem. We would love to hear if you found any solution as well.

I've copied your issue to stackoverflow in the hopes that somebody there knows a solution: http://stackoverflow.com/questions/40907901/lombok-with-h2-in-memory-class-compilation

WORKAROUND:

As posted in the answer above, this does the trick for us:

    <dependency>
        <groupId>com.sun</groupId>
        <artifactId>tools</artifactId>
        <scope>system</scope>
        <version>1</version>
        <systemPath>${java.home}/../lib/tools.jar</systemPath>
    </dependency>

We can probably fix this more thoroughly (at the cost of not being able to use lombok in your ALIAS defs, but that's going to be hard to make work well regardless), but, lombok shouldn't even be on the classpath when you run your tests.

Meanwhile, this can be fixed using surefire to exclude lombok from the test classpath:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <classpathDependencyExcludes>org.projectlombok:lombok</classpathDependencyExcludes>
    </configuration>
</plugin>

Thanks for the replies. I'll try to exclude lombok from test classpath and see.

Hi.
I had the same problem. @LeeU1911 no need to loose nerves.
Just prepare class with static method:

package com.my.package;

import java.text.*;
class SqlFunc {
   public static java.util.Date toDate(String s, String dateFormat) throws Exception {
    return new SimpleDateFormat(dateFormat).parse(s);
  }
}

now declare it in SQL as an alias

CREATE ALIAS toDate FOR "com.my.package.SqlFunc.toDate";

and now you can call it from SQL.

The difference is that SqlFunc class will be seen at compile time for Lombok and it will cause no problems. If you are declaring a source code inside SQL it will be compiled during runtime and somehow Lombok cannot handle it. I've already lost a few hours digging into H2 sourcecode and I cannot go any further.

The best solution seems to be pre-compiling your SQL procedures, but if that's not possible for you,
another workaround we've discovered is to set the system property:
h2.javaSystemCompiler to false.

The documentation for that property reads:

System property h2.javaSystemCompiler (default: true).
Whether to use the Java system compiler (ToolProvider.getSystemJavaCompiler()) if it is available to compile user defined functions. If disabled or if the system compiler is not available, the com.sun.tools.javac compiler is used if available, and "javac" (as an external process) is used if not.

It seems like the fact that it falls back on "javac" if the Sun compiler isn't available is the reason why this worked for us. This could have other unforeseen issues depending on your environments, so if you can pre-compile your H2 procedures, that might be better.

Also it may depend on your project, but we had to be specific on when in the Spring lifecycle this gets set:

@Configuration
public class DemonstrationConfig implements BeanFactoryPostProcessor, PriorityOrdered {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        System.getProperties().setProperty("h2.javaSystemCompiler", "false");
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

Actually, the best solution is to not have lombok on the test classpath, as @bendem already suggested in this previous comment.

Was this page helpful?
0 / 5 - 0 ratings