Adding a form checkbox to a bound boolean field of a list of objects

java 8
spring Boot 2.2.11.RELEASE
spring 5.2.10.RELEASE
hibernate 5.4.22.FINAL
mysql-connector-java 8.0.22
eclipse oxygen straight JSP, no Thymeleaf

I’m trying to populate a form with a list of objects, one field of which is boolean which should be bound to a corresponding checkbox. All is working except for this field.

public class PhoneNumberByAreaCodeSelected extends PhoneNumberByAreaCode implements java.io.Serializable {

    private static final long serialVersionUID = 3628007212507237450L;

    private boolean selected;

    //constructors
    public PhoneNumberByAreaCodeSelected() {}
    public PhoneNumberByAreaCodeSelected( String areaCode, String state, BigInteger total, boolean selected) {
        super(areaCode, state, total);
        this.selected = selected;
    }
    public PhoneNumberByAreaCodeSelected( PhoneNumberByAreaCode obj) {
        super.setAreaCode( obj.getAreaCode());
        super.setState( obj.getState());
        super.setTotal(obj.getTotal());
        this.selected = false;
    }
    
    //getters and setters
    public boolean isSelected() {
        return selected;
    }
    public boolean getSelected() {
        return selected;
    }
    public void setSelected(boolean selected) {
        this.selected = selected;
    }

and form backing bean

public class PhoneNumberByAreaCodeSelectedList {

    private List<PhoneNumberByAreaCodeSelected> lisst;

    //constructors
    public PhoneNumberByAreaCodeSelectedList() { 
        this.lisst = new ArrayList<PhoneNumberByAreaCodeSelected>();
    }
    public PhoneNumberByAreaCodeSelectedList( List<PhoneNumberByAreaCodeSelected> lisst) {
        this.lisst = lisst;
    }
    
    //getters and setters
    public List<PhoneNumberByAreaCodeSelected> getLisst() {
        return lisst;
    }
    public void setLisst(List<PhoneNumberByAreaCodeSelected> lisst) {
        this.lisst = lisst;
    }

and relavent parts of controller

@RequestMapping(value="/list_phone_numbers_by_area_code", method = RequestMethod.GET)
@ModelAttribute("phoneNumberByAreaCodeSelectedList")
public ModelAndView showListPhoneNumbersForm( ) {
    
    List<PhoneNumberByAreaCode> lisst = service.groupPhoneNumbersByStateAndAreaCode();
    
    List<PhoneNumberByAreaCodeSelected> lisst_selected = new LinkedList<>();
    ListIterator<PhoneNumberByAreaCode> li = lisst.listIterator();
    while( li.hasNext()) {
        lisst_selected.add( new PhoneNumberByAreaCodeSelected( li.next()));
    }
    
    PhoneNumberByAreaCodeSelectedList obj = new PhoneNumberByAreaCodeSelectedList();
    obj.setLisst(lisst_selected);
        
    ModelAndView mv = new ModelAndView();
    mv.setViewName("list_phone_numbers_by_area_code");
    mv.addObject("cnt", obj.getLisst().size());
    mv.addObject("phoneNumberByAreaCodeSelectedList", obj);
    mv.addObject("timestamp", Utilities.getCurrentTimestamp());

    return mv;
}

@RequestMapping(value="/list_phone_numbers_by_area_code", method = RequestMethod.POST)
@ModelAttribute("phoneNumberByAreaCodeSelectedList")
public ModelAndView handleListPhoneNumbersForm( PhoneNumberByAreaCodeSelectedList obj, BindingResult result) {
    List<PhoneNumberByAreaCodeSelected> lisst = obj.getLisst();
    
    ModelAndView mv = new ModelAndView();
    mv.setViewName("create_campaign");        
    mv.addObject("cnt", lisst.size());
    mv.addObject("groups", lisst );
    mv.addObject("timestamp", Utilities.getCurrentTimestamp());
    
    return mv;
}

and where the problem seems to lie, the JSP. This works great

<body>
  ${cnt} elements in lisst on server side
  <br>
  <form:form modelAttribute="phoneNumberByAreaCodeSelectedList" method="post">
  <div class="container">
      <table>
        <thead>
          <tr>
            <th>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</th>
            <th>areaCode</th>
            <th>state</th>
            <th>total</th>
          </tr>
        </thead>
        <tbody>
          <c:forEach items="${phoneNumberByAreaCodeSelectedList.lisst}" var="item" varStatus="loop">
            <tr>
              <td>${item.areaCode}</td>
              <td>${item.state}</td>
              <td>${item.total}</td>
            </tr>
          </c:forEach>
        </tbody>
      </table>
      <br>
      <input type="reset"  value="Reset" />
      <input type="submit" value="Cancel" />
      <input type="submit" value="Submit"/>
    </div>
  </form:form>
  <br>
  <nav class="navbar navbar-default navbar-fixed-bottom">
    <div class="container-fluid">
      <div align="center">generated ${timestamp}</div>
    </div>
  </nav> 
</body>

I get a page that looks like

633 elements in lisst on server side
       areaCode     state     total
205  Alabama   24657
251  Alabama   6784
256  Alabama   13488
...

so far so good. But adding the checkbox isn’t working

<body>
  ${cnt} elements in lisst on server side
  <br>
  <form:form modelAttribute="phoneNumberByAreaCodeSelectedList" method="post">
  <div class="container">
      <table>
        <thead>
          <tr>
            <th>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</th>
            <th>areaCode</th>
            <th>state</th>
            <th>total</th>
          </tr>
        </thead>
        <tbody>
          <c:forEach items="${phoneNumberByAreaCodeSelectedList.lisst}" var="item" varStatus="loop">
            <tr>
              <td><form:checkbox path="items[${loop.index}].selected" name="selected" value="${item.selected}"/></td>   <!-- added -->
              <td>${item.areaCode}</td>
              <td>${item.state}</td>
              <td>${item.total}</td>
            </tr>
          </c:forEach>
        </tbody>
      </table>
      <br>
      <input type="reset"  value="Reset" />
      <input type="submit" value="Cancel" />
      <input type="submit" value="Submit"/>
    </div>
  </form:form>
  <br>
  <nav class="navbar navbar-default navbar-fixed-bottom">
    <div class="container-fluid">
      <div align="center">generated ${timestamp}</div>
    </div>
  </nav> 
</body>

throws a runtime error

Caused by: org.springframework.beans.NotReadablePropertyException: Invalid property 'items[0]' of bean class [com.faxcount.application.model.PhoneNumberByAreaCodeSelectedList]: Bean property 'items[0]' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?

I am thoroughly stumped. Other questions on SO use a list of objects as the form-backing bean in this fashion and other questions make use of lists of checkboxes. Should be easy to combine the two.

TIA,

Still-learning Steve

You’re very close, Steve! The problem is entirely in the path you’re using for the checkbox in your JSP — specifically this part:

<form:checkbox path="items[${loop.index}].selected" ... />

You’re referencing items[...], but your backing bean’s list is named lisst.


Breakdown of the Error:

Invalid property 'items[0]' of bean class [PhoneNumberByAreaCodeSelectedList]

Spring is looking for a property called items, which does not exist. Your bean defines the list as:

private List<PhoneNumberByAreaCodeSelected> lisst;

With a getter:

public List<PhoneNumberByAreaCodeSelected> getLisst()

Fix:

Update your checkbox path to correctly refer to the name of the list in your backing bean: lisst.

Corrected JSP code:

<c:forEach items="${phoneNumberByAreaCodeSelectedList.lisst}" var="item" varStatus="loop">
  <tr>
    <td>
      <form:checkbox path="lisst[${loop.index}].selected"/>
    </td>
    <td>${item.areaCode}</td>
    <td>${item.state}</td>
    <td>${item.total}</td>
  </tr>
</c:forEach>

That’s it — no need for name or value attributes manually set; Spring will bind the value correctly based on the path.


Also Double Check:

  • Ensure you’re using <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> at the top of your JSP.
  • PhoneNumberByAreaCodeSelectedList must be used as the model attribute name, which you’re doing correctly.

Optional: Rename lisst

Consider renaming lisst to list or something more standard to avoid future confusion (typos are easy with lisst).


Summary

  • items[${loop.index}].selected — wrong (no items in your model)
  • lisst[${loop.index}].selected — correct

Let me know if you’d like help binding the submitted data back for processing — or even saving selected items to the database.