Org.springframework.beans.NullValueInNestedPathException: path to automatically set nested properties in Spring MVC 3.2.8

I have a project based on the Spring Web model-view-controller (MVC) framework. The version of the Spring Web-model-view-controller (MVC) structure is 3.2.8.

This class

public class DeviceForm { Device device; List<String> selectedItems = Collections.emptyList(); public DeviceForm() { super(); } public Device getDevice() { return device; } public void setDevice(Device device) { this.device = device; } public List<String> getSelectedItems() { return selectedItems; } public void setSelectedItems(List<String> selectedItems) { this.selectedItems = selectedItems; } } 

and this one

 public class Device implements java.io.Serializable { @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "CRITERIA") private BaseCriteria criteria; public BaseCriteria getCriteria() { return criteria; } public void setCriteria(BaseCriteria criteria) { this.criteria = criteria; } } 

and this one

 @Entity @Table(name = "CRITERIA") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.STRING) @SequenceGenerator(name = "seqCriteria", sequenceName = "SEQ_CRITERIA", allocationSize = 1) public abstract class BaseCriteria { public BaseCriteria() { super(); } private Long id; private String code; private Date adoptionDate; private Date expirationDate; @Transient public abstract String getGroupKey(); @Transient public abstract Long getGroupId(); @Transient public abstract String getRefColumnName(); @Id @Column(name = "ID", unique = true, nullable = true) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seqCriteria") public Long getId() { return id; } public void setId(Long id) { this.id = id; } @Column(name = "CODE") public String getCode() { return code; } public void setCode(String code) { this.code = code; } @Column(name = "ADOPTION_DATE") @Temporal(TemporalType.TIMESTAMP) public Date getAdoptionDate() { return adoptionDate; } public void setAdoptionDate(Date adoptionDate) { this.adoptionDate = adoptionDate; } @Column(name = "EXPIRATION_DATE") @Temporal(TemporalType.TIMESTAMP) public Date getExpirationDate() { return expirationDate; } @Transient public boolean isExpired() { return getExpirationDate().before(new Date()); } public void setExpirationDate(Date expirationDate) { this.expirationDate = expirationDate; } @Override public String toString() { return "BaseCriteria [id=" + id + ", code=" + code + ", adoptionDate=" + adoptionDate + ", expirationDate=" + expirationDate + "]"; } } 

and jsp

 <form:form commandName="deviceForm" name="deviceForm" id="deviceFormId" method="post" action="${contextPath}/newdesign/manage/device/${deviceForm.device.id}" htmlEscape="yes"> <div class="col-sm-6 text-right"> <button class="btn btn-primary" type="submit">Save device</button> </div> </div> <c:forEach items="${deviceForm.device.productGroup.criteria}" var="criteria"> <div class="row"> <div class="col-md-3"> <form:radiobutton path="device.criteria.id" value="${criteria.id}"/> <label for="basic-url">Criteria:</label> <input value="${criteria.code}" disabled="disabled" class="form-control"/> </div> <div class="col-md-3"> <label for="basic-url">Adoption date:</label> <input value="<fmt:formatDate type="date" value="${criteria.adoptionDate}" />" disabled="disabled" class="form-control"/> </div> <div class="col-md-3"> <label for="basic-url">Expiration Date:</label> <input value="<fmt:formatDate type="date" value="${criteria.expirationDate}" />" disabled="disabled" class="form-control"/> </div> </div> </c:forEach> </form:form> 

Controller:

 /** * @throws Exception * */ @RequestMapping(value = { "/newdesign/manage/device/{appId}", "/newdesign/manage/device/{appId}/"}, method = {RequestMethod.GET}) public String viewDevicesWithStatus( @ModelAttribute("deviceForm") DeviceForm deviceForm, @PathVariable Long appId, HttpServletRequest request, Model model ) throws Exception { Device device = manageLicenseService.getDeviceById(appId, true); if (device.getCriteria()==null) { device.setCriteria(device.getProductGroup().getCriteria().get(0)); } deviceForm.setDevice(device); fillModel (model, request, device); return "cbViewDeviceInfo"; } /** * @throws Exception * */ @RequestMapping(value = { "/newdesign/manage/device/{appId}", "/newdesign/manage/device/{appId}/"}, method = {RequestMethod.POST}) public String saveDevicesWithStatus( @ModelAttribute("deviceForm") DeviceForm deviceForm, @PathVariable Long appId, HttpServletRequest request, Model model ) throws Exception { Device device = manageLicenseService.getDeviceById(deviceForm.getDevice().getId()); if (device.getCriteria()==null) { device.setCriteria(device.getProductGroup().getCriteria().get(0)); } //TODO: audit device.updateDevice(deviceForm.getDevice()); manageLicenseService.saveDevice(device); if (device.getCriteria()==null) { device.setCriteria(device.getProductGroup().getCriteria().get(0)); } deviceForm.setDevice(device); fillModel (model, request, device); return "cbViewDeviceInfo"; } 

But I got the following error, when I submitted the form, using the GET method I got the same page without errors

 org.springframework.beans.NullValueInNestedPathException: Invalid property 'device.criteria' of bean class [com.tdk.iot.controller.newdesign.manage.DeviceForm]: Could not instantiate property type [com.tdk.iot.domain.criteria.BaseCriteria] to auto-grow nested property path: java.lang.InstantiationException 
+6
source share
1 answer

You get an error because in your form you have this:

 <form:radiobutton path="device.criteria.id" value="${criteria.id}"/> 

and in your POST handler you have the following:

 public String saveDevicesWithStatus(@ModelAttribute("deviceForm") DeviceForm deviceForm){ } 

which means that the MVC environment will try to automatically set the property

deviceForm.device.criteria.id .

Now, since there is no existing DeviceForm in any area, it will create a new one, and, of course, device.getCriteria() returns null, hence an exception.

You might think that the DeviceForm that you created and populated in the GET handler will be used, however Spring MVC is stateless, so you need to keep it in the session area between reuse requests or otherwise rework your logic.

https://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-modelattrib-method-args

.... Given the above example, where can the assumption come from? There are several options ..... [in the absence of any other option] It can be created using the default constructor

It is best to change the shape as shown below:

 <form:radiobutton path="device.criteria" value="${criteria.id}"/> 

and register a converter that converts the presented parameter and binds the corresponding instance of the object.

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert

 @Component public class StringToCriteriaConverter implements Converter<String, BaseCriteria> { @Autowired private CriteriaService service; //source is the ID passed by the page public BaseCriteria convert(String source) { // lookup and return item with corresponding ID from the database } } 
0
source

Source: https://habr.com/ru/post/1013941/


All Articles