I'm just trying out JSF an ran into an ugly ClassCastException
.
I have a Managed Bean (CustomerBean
) that has a POJO (Customer
) where I store the user data. One of the POJO's properties is List<CathegoryType.Type> preferredCathegories
(with getter and setter). CathegoryType
is a model class which provides cathegories (through a nested enum Type
) and their localized names (through a method getCathegory(Type type)
).
Now, I have one JSF page to enter some user data (editCustomer.xhtml
). There is a section to select preferred cathegories. The JSF code to select the cathegories looks like this:
<h:selectManyListbox
id="pcat"
value="#{customerBean.customer.preferredCathegories}"
styleClass="form-control">
<f:selectItems
value="#{customerBean.cathegoryTypes}">
</f:selectItems>
</h:selectManyListbox>
The renderes component looks like this:
The field List<SelectItem> CustomerBeand.cathegoryTypes
provides the mapping of the enum literals to their names like new SelectItem(type, CathegoryType.getCathegory(type))
. Now there comes the tricky part that throws the ClassCastException
(in my understanding for no reason!)
When I submit the form an other JSF page (showCustomer.xhtml
) is should present the just entered user data. But creating the view terminates with throwing the following ClassCastException:
SCHWERWIEGEND: Servlet.service() for servlet [Faces Servlet] in context with path [/...] threw exception [java.lang.String cannot be cast to ...CathegoryType$Type] with root cause
java.lang.ClassCastException: java.lang.String cannot be cast to ...CathegoryType$Type
at ...CustomerBean.getNamedPreferredCathegories(CustomerBean.java:247)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
...
To present the chosen cathegories, showCustomer.xhtml
calls a method String CustomerBean.getNamedPreferredCathegories()
:
<h:outputText value="#{customerBean.namedPreferredCathegories}" />
This method calculates a String
of the chosen cathegories:
def String getNamedPreferredCathegories() {
val StringBuilder names = new StringBuilder
val List<CathegoryType.Type> cathegories = customer.preferredCathegories
val ListIterator<CathegoryType.Type> iter = cathegories.listIterator
var boolean first = true
while (iter.hasNext) {
val CathegoryType.Type cat = iter.next
val String name = cathegoryType.getCathegory(cat)
if(first) first = false else names.append(', ')
names.append(name)
}
return names.toString
}
I'm using the Xtend programming languate. It is a JVM language (compiles to Java code) and thus fully compatible with the Java typesystem. I tried to write this method as Java like as possible, normally it can be done in one single line:
def String getNamedPreferredCathegories() {
'''«FOR name : customer.preferredCathegories.map[cathegory] SEPARATOR ', '»«name»«ENDFOR»'''.toString
}
Since I do any iteration over the list, the ClassCastException
is thrown ... but as one can see, I do not perform any cast operation in my code! So where does the exception comes from?
I run the project on Tomcat 8.5.9.
Edit: Ok, the generated Java method of my Xtend method is theo following:
public String getNamedPreferredCathegories() {
final StringBuilder names = new StringBuilder();
final List<CathegoryType.Type> cathegories = this.customer.getPreferredCathegories();
final ListIterator<CathegoryType.Type> iter = cathegories.listIterator();
boolean first = true;
while (iter.hasNext()) {
{
final CathegoryType.Type cat = iter.next(); // Exception is thrown here!
final String name = this.cathegoryType.getCathegory(cat);
if (first) {
first = false;
} else {
names.append(", ");
}
names.append(name);
}
}
return names.toString();
}
The exception is thrown in the marked line (final CathegoryType.Type cat = iter.next();
). Additionally the generated Java parts of the related Customer
class are this:
public class Customer {
private List<CathegoryType.Type> preferredCathegories;
public List<CathegoryType.Type> getPreferredCathegories() {
return this.preferredCathegories;
}
public void setPreferredCathegories(final List<CathegoryType.Type> preferredCathegories) {
this.preferredCathegories = preferredCathegories;
}
}
Additionally, I will have a look at the linked question that might discuss the exact same problem.