Search code examples
javacachingjakarta-eeguava

NoSuchMethodError (checkState) in CacheBuilder


I have created a Guava (a library packed with useful stuff that can be very handy in almost any Java project, including libraries for immutable collections, functional programming, I/O and much more) CacheBuilder based cache

 LoadingCache<String, Byte[]> companyDevicesCache = 
             CacheBuilder.newBuilder()
                .maximumSize(1000)                     // maximum 100 records can be cached
                .expireAfterAccess(24, TimeUnit.HOURS) // cache will expire after 30 minutes of access
                .build(new CacheLoader<String, Byte[]>(){ // build the cacheloader

                   @Override
                   public Byte[] load(String companyId) throws Exception {
                      //make the expensive call
                      return getFromServletContext (companyId);
                   }


                });

I've tried also

CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES)
         .maximumSize(1000)
         .build(new CacheLoader<String, List<String>>() {
             @Override
             public List<String> load(String queryKey) throws Exception {
                 return null;
             }
         });

and also

CacheLoader loader = new CacheLoader()
            {
                public String load(String key) throws Exception
                {
                    return null;
                }

                @Override
                public Object load(Object arg0) throws Exception {
                    // TODO Auto-generated method stub
                    return null;
                }
            };

         LoadingCache<String, String> persons = CacheBuilder.newBuilder() 
                 .initialCapacity(30) 
                 .maximumSize(40) 
                 .recordStats() 
                 .build(loader);

and

final LoadingCache<String, Optional<Product>> cache =


               CacheBuilder.newBuilder()
                   .expireAfterWrite(1, TimeUnit.DAYS)
                   .refreshAfterWrite(1, TimeUnit.SECONDS)
                   .build( new CacheLoader<String, Optional<Product>>() {
                         @Override
                         public Optional<Product> load( String productId ) throws Exception {
                             return null;
                         }
                   }
             );

But when I init the CacheBuilder I got this error:

]] Root cause of ServletException.
java.lang.NoSuchMethodError: com.google.common.base.Preconditions.checkState(ZLjava/lang/String;J)V
        at com.google.common.cache.CacheBuilder.maximumSize(CacheBuilder.java:409)

this is the version of the library: guava-20.0-rc1.jar

Here the libs included in the ear

enter image description here

I also tried to replace the library for this one guava-19.0.jar but then I got

]] Root cause of ServletException.
java.lang.IncompatibleClassChangeError: Found interface com.google.common.base.Equivalence, but class was expected
        at com.google.common.cache.LocalCache$Strength$1.defaultEquivalence(LocalCache.java:393)
        at com.google.common.cache.CacheBuilder.getKeyEquivalence(CacheBuilder.java:297)
        at com.google.common.cache.LocalCache.<init>(LocalCache.java:246)
        at com.google.common.cache.LocalCache$LocalLoadingCache.<init>(LocalCache.java:4868)
        at com.google.common.cache.CacheBuilder.build(CacheBuilder.java:786)

Solution

  • It really looks like you have multiple versions of the com.google.common.base.Preconditions class on the classpath (maybe from a reeeeally old google-collections jar?), or a corrupted jar.

    The method overload which isn't found has been added in Guava 20.0:

    public static void checkState(boolean b, @Nullable String errorMessageTemplate, long p1)
    

    based on the signature in the NoSuchMethodError:

    checkState(ZLjava/lang/String;J)V
               ||                 | |
               ||                 | returns void
               ||                 |
               ||                 long
               |String
               |
               boolean
    

    You can find where your class definition comes from by adding the following code before your call to CacheBuilder.maximumSize():

    System.out.println(com.google.common.base.Preconditions.class
            .getProtectionDomain()
            .getCodeSource()
            .getLocation()
            .toExternalForm());
    

    You'll get the URL of the jar.

    I've downloaded guava-20.0-rc1.jar from Maven Central, extracted its content and inspected the content of Preconditions.class using javap, and the method is definitely there:

    $ javap Preconditions.class
    Compiled from "Preconditions.java"
    public final class com.google.common.base.Preconditions {
      // ...
      public static void checkState(boolean, java.lang.String, long);
      // ...
    }
    

    In your ant snippet, there are multiple wildcard includes, such as:

    <include name="**/spring/*.jar"/>
    

    There might be an extra version of Guava in there, especially considering the other error you get when switching to guava-19.0: Equivalence was an interface until 9.0, and was changed in 2011 to a class for 10.0.