ClassCastException: {generic} type cannot be cast to {required} type

309 views
Skip to first unread message

M

unread,
Feb 3, 2020, 7:40:16 PM2/3/20
to equalsverifier
Reference: https://jqno.nl/equalsverifier/errormessages/recursive-datastructure/ (Generics section)

I have a recursive datastructure that inherits a generically typed field, like so:

public abstract class First<T extends Comparable<T>> {
    private T planningId;

    public T getPlanningId() {
        return planningId;
    }

    public void setPlanningId(T planningId) {
        this.planningId = planningId;
    }
}

public abstract class Second extends First<String> {
    @Override
    public void setPlanningId(String planningId) {
        (...)
    }
}

public final class Third extends Second {
    @Override
    public String getPlanningId() {
        (...)
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Third that = (Third) o;

        if (!Objects.equals(getPlanningId(), that.getPlanningId())) return false;

        (...)
    }

    @Override
    public int hashCode() {
        int result;

        result = getPlanningId() != null ? getPlanningId().hashCode() : 0;

        (...)
    }
}

Here's the error that I'm getting:

java.lang.AssertionError: EqualsVerifier found a problem in class Third.
-> Generics: ClassCastException was thrown. Consider using withGenericPrefabValues for String.


at nl.jqno.equalsverifier.EqualsVerifierApi.verify(EqualsVerifierApi.java:292)
at EqualsTest.lambda$test$2(EqualsTest.java:142)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at EqualsTest.test(EqualsTest.java:142)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: nl.jqno.equalsverifier.internal.exceptions.AssertionException
at nl.jqno.equalsverifier.internal.util.Assert.fail(Assert.java:75)
at nl.jqno.equalsverifier.internal.checkers.fieldchecks.NullPointerExceptionFieldCheck.classCastExceptionThrown(NullPointerExceptionFieldCheck.java:88)
at nl.jqno.equalsverifier.internal.checkers.fieldchecks.NullPointerExceptionFieldCheck.handle(NullPointerExceptionFieldCheck.java:64)
at nl.jqno.equalsverifier.internal.checkers.fieldchecks.NullPointerExceptionFieldCheck.performTests(NullPointerExceptionFieldCheck.java:50)
at nl.jqno.equalsverifier.internal.checkers.fieldchecks.NullPointerExceptionFieldCheck.execute(NullPointerExceptionFieldCheck.java:42)
at nl.jqno.equalsverifier.internal.checkers.FieldInspector.check(FieldInspector.java:26)
at nl.jqno.equalsverifier.internal.checkers.NullChecker.check(NullChecker.java:23)
at nl.jqno.equalsverifier.EqualsVerifierApi.verifyWithoutExamples(EqualsVerifierApi.java:368)
at nl.jqno.equalsverifier.EqualsVerifierApi.performVerification(EqualsVerifierApi.java:337)
at nl.jqno.equalsverifier.EqualsVerifierApi.verify(EqualsVerifierApi.java:290)
... 26 more
Caused by: java.lang.ClassCastException: class nl.jqno.equalsverifier.internal.reflection.Comparable$$DynamicSubclass$146589023 cannot be cast to class java.lang.String (nl.jqno.equalsverifier.internal.reflection.Comparable$$DynamicSubclass$146589023 is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap')
at Third.getPlanningId(Third.java:75)
at Third.hashCode(Third.java:305)
at nl.jqno.equalsverifier.internal.util.CachedHashCodeInitializer.getInitializedHashCode(CachedHashCodeInitializer.java:69)
at nl.jqno.equalsverifier.internal.checkers.fieldchecks.NullPointerExceptionFieldCheck.lambda$performTests$2(NullPointerExceptionFieldCheck.java:53)
at nl.jqno.equalsverifier.internal.checkers.fieldchecks.NullPointerExceptionFieldCheck.handle(NullPointerExceptionFieldCheck.java:58)
... 33 more

I was suggested to consider using withGenericPrefabValues for String which doesn't make sense because it's not the String type that the engine isn't able to create prefabs for but rather it's the Comparable type.
My guess is that the engine resolved the type of planningId to be Comparable<Object> because the best it could tell without knowing of the implementations of the First class is that T extends Comparable.

Is this something that the engine isn't capable of doing? Or can I still call withGenericPrefabValues with the Comparable.class specified and somehow providing a generic factory that would produce a Comparable castable to a String? <- seems impossible to me.
There was a work around I though would work - just make the factory always return Strings, but then that didn't work because I also have another class I'm verifying that extends the First class which specifies a Long type for the Comparable generics and holds a collection of the Third classes (so there's a mix of First<String> and First<Long> instances).

Thanks!

Jan Ouwens

unread,
Feb 6, 2020, 7:10:14 AM2/6/20
to equalsv...@googlegroups.com, M
Hi,

Thanks for letting me know about this. It seems likely that EqualsVerfier got confused by the inheritance, where the type parameter was initially bounded to Comparable which then got specified into String.

I'm not able to look into this properly at the moment. I'll try to take a look next week though.



Jan
--
You received this message because you are subscribed to the Google Groups "equalsverifier" group.
To unsubscribe from this group and stop receiving emails from it, send an email to equalsverifie...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/equalsverifier/aab51a89-16fa-4b17-9243-26e285024eb8%40googlegroups.com.

Jan Ouwens

unread,
Feb 11, 2020, 11:26:24 AM2/11/20
to equalsv...@googlegroups.com, M
Hi,

I've looked into it further. The problem has to do with type subtleties around the `planningId` field and the `getPlanningId()` method. The `planningId` field has type `T`, which is a `T extends Comparable<T>` (or `Comparable<T>`, for simplicity). This is fixated to `String` in the `Third` subclass, but when EqualsVerifier determines the field's type, it still gets `Comparable<T>`.

In the mean time, there is the `getPlanningId()` method, which is also overridden, and which returns `String` in the `Third` subclass.

When EqualsVerifier tries to construct a value for `planningId`, it creates an instance of `Comparable<T>`. Or rather, it generates bytecode for a type that implements that interface, much in the same way that mocking frameworks do it. This is fine, most of the time.

However, the `getPlanningId()` method is called in `Third`'s `equals` method, and that needs to return a `String`. It doesn't match the `T extends Comparable<T>` from `First` with the `String` from `Third`, because it's just too hard to do so. (Java's generics have a surprising amount of corner cases; it would require re-implementing large parts of the compiler to make that match.)

That means it's probably not feasible to make a general fix for this issue. At least, I wouldn't know how to do that.

Now I can't see what `getPlanningId()` does in the `Third` override, but if `equals` could simply call the original `getPlanningId()` from `First`, or if `First` could be the class to implement `equals`, then the problem would simply go away.

In other words, the only thing I can offer you is a workaround. If that's unsatisfying for you, I can understand -- feel free to open a ticket in the issue tracker. However, I don't think I'll be able to fix it any time soon, or even at all. Although I'm definitely open for a good pull request :).


Regards,
Jan



On 4 February 2020 at 01:40:19, M (melvi...@ca.abb.com) wrote:

John Camerin

unread,
Mar 16, 2020, 10:43:42 AM3/16/20
to equalsv...@googlegroups.com
I'm a big fan of equalsverifier and up until moving into 3.1 and JPA support, its been a huge value add. 
As I try to migrate to 3.1 and JPA support, I am running into some issues for my entities with composite keys. The basic case is an entity which uses some, but not all of the fields of the class to uniquely identify it to the database. Here, none of the fields in the composite key are surrogates. However, the composite key also does not include all of the fields representing state of the object. In this case, equalsverifier complains about use of the id if the composite key fields are used in equals and hashcode and complains about the other fields which are not part of the composite key if I use SUPPRESS_SURROGATE_KEY.
So, it seems like equalsverifier supports and either/or, either the id represents equality or not. The equalsverifier code seems to not support the case I have (which I would expect to be common) where the state of the object is represented by all of the (non-transient) fields, some of which are part of the id to the database. 
Am I misunderstanding something here?  Code below... note the value field, which is part of the state, but not part of the composite key.

Thank you
John

@Entity
@Table(name = "customer_product_configuration", catalog = "provisioning")
@IdClass(ProductConfigurationCompositeKey.class)
public class ProductConfiguration implements Serializable {
private static final long serialVersionUID = 2274295590074690571L;

@Id
@Column(name = "customer_id")
private long customerId;

@Id
@Column(name = "product_name")
@Enumerated(EnumType.STRING)
private ProductType productName;

@Id
@Column(name = "configuration_key")
@Enumerated(EnumType.STRING)
private ConfigurationType key;

@Column(name = "value")
private String value;

    ...  getters/setters

@Override
public boolean equals(final Object o) {

if (this == o) {
return true;
}
    if (!(o instanceof ProductConfiguration)) {
return false;
}
final ProductConfiguration that = (ProductConfiguration) o;
return Objects.equals(customerId, that.customerId)
&& Objects.equals(productName, that.productName)
&& Objects.equals(key, that.key)
&& Objects.equals(value, that.value);
}

@Override
public int hashCode() {
return Objects.hash(customerId, productName, key, value);
}

public class ProductConfigurationCompositeKey implements Serializable {

private static final long serialVersionUID = -6413752983550618565L;
private long customerId;
private ProductType productName;
private ConfigurationType key;

@Override
public boolean equals(Object that) {
return that.getClass() == getClass()
&& EqualsBuilder.reflectionEquals(this, that);
}

@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}

Jan Ouwens

unread,
Mar 17, 2020, 4:54:28 AM3/17/20
to equalsv...@googlegroups.com
Hi John,

Thanks for reaching out, and for the kind words! I'm glad you find EqualsVerifier useful.

I think you're right, I haven't considered composite ids when I added the support for Hibernate. I have to admit I haven't used Hibernate a lot myself, so I primarily followed advice from members of this mailinglist and people who registered tickets, who of course all had their own workflows. So that's on me for not researching better, I guess :).

Just for my understanding: what happens when two objects have the same composite id, but a different `value`, in the case of your `ProductConfiguration` class? You can't put them both into the database, I assume?

I'm happy to add support for this in EqualsVerifier, but given the current state of the world, it might be a while: my wife and I have to work from home with no daycare for our daughter, so spare time is extremely limited right now. PR's are always welcome though ;).


Regards,
Jan
> --
> You received this message because you are subscribed to the Google Groups "equalsverifier" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to equalsverifie...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/equalsverifier/CAD2EMZG6_Pr-PsoZmyxuFr6y%3DTxZKT7Hxq61h3_EKq-ph%3D36Pg%40mail.gmail.com.

John Camerin

unread,
Mar 17, 2020, 2:32:30 PM3/17/20
to equalsv...@googlegroups.com
Thanks for the quick response Jan.  We're much in the same position here, work from home, kids at home, no daycare. So, totally get it.  I think we're all just trying to figure it out. 
So, I did read through the original ticket discussion and the reference blog from Vlad Mihalcea (I've sent that to my team).  And best I can tell, the case I am presenting wasnt in focus at the time. The blog from Vlad doesnt even address the case.
To answer your question, if there are two different objects instances with the same compositeId and different values, this behaves similarly in Hibernate when there's a surrogate key such as a sequence.  Hibernate will complain if the app tries to persist the second instance because of an existing entity with the id already exists.  The option is to merge or to retrieve the existing entity and update its state.
Lastly, I would be happy to contribute a PR. Least I can do for something I am using for free to make my code better. Please let me know next steps for contribution if there's anything more to do than just submit the PR.

Thank you
John


Jan Ouwens

unread,
Mar 18, 2020, 4:32:44 AM3/18/20
to equalsv...@googlegroups.com
Hi John,

So, if I understand you correctly, if two objects have the same composite id, but different values, the db will reject the second instance when it's persisted. That makes sense. But where the application is concerned, they are still equal? Why is that?

A PR is much appreciated. I don't have any specific requirements other than the PR itself. I think they best way to start would be to open up the JpaIdTest.java file and start exploring from there. It's pretty big already, so I think it would be nice if you add your test cases in a new file, JpaCompositeIdTest or something like that. Just add test cases and see where you need to edit code to make things work -- that's how I usually approach a feature like this :).

Also, in case you're wondering when you're browsing the code, I've re-created the relevant persistence annotations to avoid pulling in too many dependencies. EqualsVerifier actually only checks annotations using a `contains` or `endsWith` function, not an `equals`, to make that work.

And please, take your time. We are indeed trying to figure it all out, and family comes first in my opinion. Hopefully things will get back to normal soon.

If you have any questions, don't hesitate to ask.


Cheers,
Jan
> --
> You received this message because you are subscribed to the Google Groups "equalsverifier" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to equalsverifie...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/equalsverifier/CAD2EMZH1Qga5UzKzhcWU1QqbOFJ1FXpQxTJ_o9Ft4%2BstDUm-kg%40mail.gmail.com.

John Camerin

unread,
Mar 19, 2020, 9:22:02 PM3/19/20
to equalsv...@googlegroups.com
Thanks Jan.  Hope you and your family are staying safe.

I've started diving in.  You are right about the JpaIdTest class.

About your question.... if two objects have the same composite id and different values, then
1) Because the field value is considered part of the state, the two objects then have different state. Therefore by the guidance for equals and hashCode, these objects should not be equal and should have different hashCodes.
2) If the application tries through Hibernate to persist the second object, Hibernate will throw an exception because of the first object, which as far as Hibernte is concerned has the same identity. Hibernate will refuse to persist a second entity with the same identity. The application can merge the second object through Hibernate, which will update the first with the value from the second.

John


Jan Ouwens

unread,
Mar 20, 2020, 8:11:44 AM3/20/20
to equalsv...@googlegroups.com
Hi John,

Thanks for the reply -- that does clarify things for me.
Good luck on the PR, and again, don't hesitate to ask if you have any questions.


Jan
> To view this discussion on the web visit https://groups.google.com/d/msgid/equalsverifier/CAD2EMZEgCb9V4Zu06md-CcvknnahhNz4BQ%3DROpAvYVWbLnWgmw%40mail.gmail.com.

Reply all
Reply to author
Forward
0 new messages