ajaxjsfjsf-2.3selectmanycheckbox

ClassCastException for <h:selectManyCheckbox> with validation on ajax update when INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL is active


I have a <h:selectManyCheckbox> that has a required-validation on. If I submit the form, I get a validation error when nothing is selected. So far, this ist expected. However, if I do an ajax update on the checkbox then, I get a ClassCastException. But only if empty values are treated as null.

So, I have the following setup. In the web.xml I set

<context-param>
  <param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
  <param-value>true</param-value>
</context-param>

Then I have an xhtml-page like this:

<h:form id="main">
  <h:selectManyCheckbox id="value" value="#{testcb.selected}" required="true" requiredMessage="Select at least one entry">
    <f:selectItems value="#{testcb.available}"/>
  </h:selectManyCheckbox>
  <div><h:message for="value" style="color:red;"/></div>

  <h:outputLabel for="checkit" value="Enter some text: "/>
  <h:inputText id="checkit" value="#{testcb.text}">
    <f:ajax event="change" execute="@this" render=":main:value"/>
  </h:inputText>

  <div><h:commandButton type="submit" value="Submit" action="#{testcb.action}"/></div>
</h:form>

And this backing bean:

@Named("testcb")
@SessionScoped
public class TestCBBean implements Serializable {

  private final Set<TestValue> available = EnumSet.allOf(TestValue.class);
  private final Set<TestValue> selected = EnumSet.noneOf(TestValue.class);
  private String text;

  public void action() {}

  public Set<TestValue> getAvailable() { return available; }

  public void setAvailable(Set<TestValue> available) {
    this.available.clear();
    this.available.addAll(available);
  }

  public Set<TestValue> getSelected() { return selected; }

  public void setSelected(Set<TestValue> selected) {
    this.selected.clear();
    this.selected.addAll(selected);
  }

  public String getText() { return text; }

  public void setText(String text) { this.text = text; }
}

And this enum:

public enum TestValue { ONE, TWO, THREE }

I am running this in Wildfly 26.0.1-Final (JavaEE 8). But this also happens in older versions (like Wildfly 15). What I am doing:

screenshot of UI

  1. enter some text and leave the box: an ajax update runs setting the value successfully in the model
  2. I press submit: the validation error for the empty checkboxes pops up
  3. I modify the text in the input and leave the box: the ajax update results in the following Exception:
    java.lang.ClassCastException: class java.lang.String cannot be cast to class [Ljava.lang.Object; (java.lang.String and [Ljava.lang.Object; are in module java.base of loader 'bootstrap')
      com.sun.jsf-impl@2.3.17.SP01//com.sun.faces.renderkit.html_basic.MenuRenderer.getSubmittedSelectedValues(MenuRenderer.java:508)
      com.sun.jsf-impl@2.3.17.SP01//com.sun.faces.renderkit.html_basic.SelectManyCheckboxListRenderer.encodeEnd(SelectManyCheckboxListRenderer.java:89)
      javax.faces.api@3.1.0.SP01//javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:600)
      javax.faces.api@3.1.0.SP01//javax.faces.component.UIComponent.encodeAll(UIComponent.java:1655)
      com.sun.jsf-impl@2.3.17.SP01//com.sun.faces.context.PartialViewContextImpl$PhaseAwareVisitCallback.visit(PartialViewContextImpl.java:628)
      com.sun.jsf-impl@2.3.17.SP01//com.sun.faces.component.visit.PartialVisitContext.invokeVisitCallback(PartialVisitContext.java:159)
      javax.faces.api@3.1.0.SP01//javax.faces.component.UIComponent.visitTree(UIComponent.java:1457)
      javax.faces.api@3.1.0.SP01//javax.faces.component.UIComponent.visitTree(UIComponent.java:1469)
      javax.faces.api@3.1.0.SP01//javax.faces.component.UIForm.visitTree(UIForm.java:355)

On the ajax update the checkboxes are not submitted. But they seem to contain an empty string as submitted value from the validation step before.

When setting the context parameter to false this works. But I want to keep it on true. Any ideas how I could work around this problem?


Solution

  • Reproduced. This is indeed a bug in Mojarra.

    It boils down to that the following method in UIInput superclass ...

    @Override
    public Object getSubmittedValue() {
        if (submittedValue == null && !isValid() && considerEmptyStringNull(FacesContext.getCurrentInstance())) {
            return "";
        } else {
            return submittedValue;
        }
    }
    

    ... is not overridden in UISelectMany superclass in such way that it returns new String[0] instead of "". This was an oversight during implementing Faces issue 671.

    I have fixed it in Mojarra issue 5081.

    In the meanwhile, until you can upgrade to the Mojarra version containing the fix, you can temporarily work around it by copy pasting the entire source code file of UISelectMany into your project while maintaining the package and adding the following method to it:

    @Override
    public Object getSubmittedValue() {
        Object submittedValue = super.getSubmittedValue();
        return "".equals(submittedValue) ? new String[0] : submittedValue;
    }