8

I am facing a weird issue in my application which runs on Spring Boot 1.4.0M3 which is using Spring cache implementation where provider is Redis where I receive classCastException that same object cannot be casted

I am using Mongodb as database and I have User Object which contains List of Roles object loaded lazily and Roles internally contains Permissions Object like below

@Document
@Data
public class User implements Serializable{
private String passwordResetToken;

private boolean enabled = false;

@DBRef(lazy= true)
private List<Role> roleList;
}

My Role DTO is as below

@Data
@Document
public class Role implements Serializable{
   private String roleName;
    private String description;
    @DBRef(lazy= true)
    private List<Permission> permissions;
}

Now in my spring MVC while loading all roles I am calling all permissions and since this is repetitive operation I thought of caching the result and using redis and while loading the roles value I receive below exception.

raised java.lang.ClassCastException: com.learning.securedapp.domain.Permission cannot be cast to com.learning.securedapp.domain.Permission

Help me to overcome this error.

I am attaching the source code to my project and I receive error at line 91 of RoleController.java

To Replicate in your local environment login to application and click on permissions menu and then roles menu, In Roles menu now click on any edit icon.you will receive above error.

rajadilipkolli
  • 3,475
  • 2
  • 26
  • 49
  • 2
    If a class gets loaded twice by different classloaders the jvm considers them as unequal. I guess thats what's happnening to you. – joshiste Jun 22 '16 at 20:43
  • Looks like you are using a war file, so the issue is probably with our application container having multiple versions of the same class loaded. – Magnus Jun 23 '16 at 04:27
  • I am running from ide, and I see only one version of permission class. – rajadilipkolli Jun 23 '16 at 04:50

4 Answers4

13

When you use DevTools with caching, you need to be aware of this limitation.

When the object is serialized into the cache, the application class loader is C1. Then after you change some code/configuration, devtools automatically restart the context and creates a new classloader (C2). When you hit that cache method, the cache abstraction finds an entry in the cache and it deserializes it from the store. If the cache library doesn't take the context classloader into account, that object will have the wrong classloader attached to it (which explains that weird exception A cannot be cast to A).

TL;DR do not serialize classes with devtools if the cache library doesn't use the context classloader. Or put your cache library in the application classloader:

restart.include.yourcache=/my-cache-lib-[\\w-]+\.jar
Stephane Nicoll
  • 31,977
  • 9
  • 97
  • 89
  • thanks @nicoll, could you please provide me a sample of how to exclude the cache library in the application classloader – rajadilipkolli Jun 23 '16 at 18:25
  • 1
    The second link has an example. You should actually _include_ the cache library in the application class loader. I'll update the issue. – Stephane Nicoll Jun 25 '16 at 10:03
  • I have followed the link you have provided and added restart.exclude.cache=jedis*.jar property under META-INF/spring-devtools.properties but I still face the same issue. I have added jedis because i am using redis as my cache provider – rajadilipkolli Jun 29 '16 at 09:03
  • that's not jedis who does the serialization I think. – Stephane Nicoll Jun 29 '16 at 14:30
  • And I am not sure that the `*` is legit either. – Stephane Nicoll Jun 29 '16 at 14:47
  • I have added below data in my properties file , still i face the same issue restart.exclude.cache=jedis[\\w-]+\.jar restart.exclude.classes=C:\\workspace-sts-3.7.0.RELEASE\\springsecuredthymeleafapp\\build\\classes\\main\\com\\learning\\securedapp\\web\\services\\SecurityServiceImpl.class – rajadilipkolli Jun 29 '16 at 15:09
  • You are using `RedisCacheManager`, that's not jedis serializing the data, but `spring-data-redis`. Excluding the wrong jar is not going to help. – Stephane Nicoll Jun 30 '16 at 08:09
  • @Nicoll , thanks for the update, I have modified my code to exclude all classes and spring-data-redis as follows restart.exclude.cache=spring-data-redis-[\\w-]+\.jar restart.exclude.classes=C:\\workspace-sts-3.7.0.RELEASE\\springsecuredthymeleafapp\\build\\classes but still I face the same issue – rajadilipkolli Jul 01 '16 at 10:07
  • you should include that jar not exclude it. (restart.include.cache=....) – Stephane Nicoll Jul 01 '16 at 14:07
  • 1
    Changing to restart.include.cache is also not working – rajadilipkolli Jul 01 '16 at 17:30
  • refer this - http://stackoverflow.com/questions/34577936/spring-boot-devtools-causing-classcastexception-while-getting-from-cache – visrahane Jan 16 '17 at 12:01
  • Wow, this limitation cost me two days of error hunting. It turns out, if you return the entities as JSON, applying a JSONView, there is no Exception, but JSON objects without any fields! – Rüdiger Schulz Oct 22 '18 at 22:01
9

This worked for me , DevTools and Redis both are working. We need to pass classLoader when creating JdkSerializationRedisSerializer and it should work

 JdkSerializationRedisSerializer redisSerializer = new JdkSerializationRedisSerializer(getClass().getClassLoader());

So my RedisCacheConfig is:

@Configuration
@EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport implements CachingConfigurer {


............................
............................


    @Bean
public RedisCacheManager redisCacheManager(LettuceConnectionFactory lettuceConnectionFactory) {
    JdkSerializationRedisSerializer redisSerializer = new JdkSerializationRedisSerializer(getClass().getClassLoader());

    RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
            .disableCachingNullValues()
            .entryTtl(Duration.ofHours(redisDataTTL))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer));

    redisCacheConfiguration.usePrefix();

    RedisCacheManager redisCacheManager = RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(lettuceConnectionFactory)
            .cacheDefaults(redisCacheConfiguration)
            .build();

    redisCacheManager.setTransactionAware(true);
    return redisCacheManager;
}

............................
............................


}

Check this spring boot issue: https://github.com/spring-projects/spring-boot/issues/9444

Mukesh M
  • 2,242
  • 6
  • 28
  • 41
3

I actually tried the proposed solution (and many variations thereof) with no luck. E.g., this didn't stop the problem from occurring:

restart.include.cache=/spring-data-redis-.*.jar

I updated the above to callout the specific version I was using and it still didn't work.

What I ended up doing which did work was to exclude spring-boot-devtools from my project. I'm using Maven so the annotation was this:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <version>[1.5.9,)</version>
        <scope>provided</scope>
    </dependency>

This will prevent any version equal to or greater than 1.5.9 from loading up. After I included the above, everything worked as expected. I know this isn't an ideal solution for all, but I made little use of the restart functions of devtools so this was actually a good approach for me.

Always Learning
  • 2,623
  • 3
  • 20
  • 39
3

I am using Spring Boot 2.0.5, and I ended up removing devtools altogether from pom.xml. Thanks to the answer above from @Always Learning.
As much as I hate to do this, but I can't find another way for now!

  <!-- 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
     -->    
C J
  • 298
  • 4
  • 13