Affects: 5.1.6.RELEASE
My Spring Controller method, which handles a post request, will throw the following exception when doing the data binding:
NullValueInNestedPathException: Invalid property 'parent' of bean class [AreaImpl]: Could not instantiate property type [Area] to auto-grow nested property path; nested exception is java.lang.NoSuchMethodException: Area.
()
The reason seems to be that the data container object has an interface as an attribute. In the below instance (and for simplicity) it is a recursively nesting attribute, but I don't think that is the problem. Since you can't have constructors for interfaces, the reflection code fails and says it can't instantiate it. I understand that it's impossible to guess at implementations, so that is not the issue here.
I read that I can disable autoGrowNestedPaths
, but that leads to a host of other problems with null objects everywhere, so that's not a solution either for me. However, I've found a workaround ! If my Entity class has two setters for the interface attribute, then the code will work without a hitch, but only if they are defined in the data container object in a specific order.
interface Area {
Area getParent();
void setParent(Area parent);
}
The following implementation will throw the above exception, whether or not setParent(AreaImpl1)
exists:
class AreaImpl1 implements Area {
Area parent;
@Override
public Area getParent() {
return parent;
}
@Override
public void setParent(Area parent) {
this.parent = parent;
}
public void setParent(AreaImpl1 parent) {
this.parent = parent;
}
}
The following implementation will give valid data binding!
class AreaImpl2 implements Area {
Area parent;
@Override
public Area getParent() {
return parent;
}
public void setParent(AreaImpl2 parent) {
this.parent = parent;
}
@Override
public void setParent(Area parent) {
this.parent = parent;
}
}
I've traced the issue into AbstractNestablePropertyAccessor.getPropertyTypeDescriptor
, but I don't know if the resulting behavior is expected or buggy. If it is expected behavior, then I haven't found any documentation describing it, so this may be a documentation bug as well.
I don't mind adding the extra method to my entire domain layer (though it is a weird thing to have to do, and there may be some problems when there are actually multiple classes that can implement the interface), but I do mind that the order is important: it can make any refactoring, automated or otherwise, very brittle, and the resulting exceptions are not at all relatable to the root cause of the issue.
Comment From: Yusuf010191
Nono
Comment From: snicoll
@blagae I am trying to better understand the problem here and could use a small sample that helps me replicate the problem. You can attach a zip to this issue or share the link to a separate GitHub repo. Thanks!
Comment From: spring-projects-issues
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Comment From: blagae
Hi @snicoll
While I must commend you for looking at this 4 year old issue, this is code from a project that I abandoned relatively soon after this, because of shifting priorities. It was also at that time already a legacy project that I had picked up again for a final time, so the codebase was always a bit of a mess.
I have no idea anymore what the issue was that I encountered beyond the information provided above; I do remember debugging into the Spring code (which I had never really done before) and pinpointing the issue, but I knew of no real alternative.
I will hereby provide you with the stripped-down implementation of the Controller and the Dao that used this data entity, However, I am unsure if it's enough and I don't think I will try and get the whole thing running again.
interface Area {
Area getParent();
void setParent(Area parent);
}
@Entity
public class AreaImpl implements Area {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(targetEntity = AreaImpl.class, cascade = CascadeType.ALL)
private Area parent;
@OneToMany(targetEntity = AreaImpl.class, cascade = CascadeType.ALL, mappedBy = "parent")
private Set<Area> children;
@Override
public Long getId() {
return id;
}
@Override
public void setId(Long t) {
id = t;
}
/* This method is here for data binding; it must be BEFORE the Area method or Spring will flip out */
// ^ this is an actual comment from 2019; as stated above, move this to the bottom of the class definition for weird results.
public void setParent(AreaImpl parent) {
this.parent = parent;
}
@Override
public void setParent(Area parent) {
this.parent = parent;
}
@Override
public Area getParent() {
return parent;
}
}
import org.springframework.data.repository.CrudRepository;
public interface GenericDao<E, ID extends Serializable> extends CrudRepository<E, ID> {
E findByName(String name);
}
import org.springframework.stereotype.Repository;
@Repository
public interface AreaDao extends GenericDao<AreaImpl, Long> {}
@Controller
@RequestMapping("/areas")
@Transactional
public class AreaController {
@Autowired
private AreaDao areaDao;
@RequestMapping(value = {"index", ""}, method = RequestMethod.GET)
public String handleMyRequest(Model model) {
registerList(model, areaDao.findAll());
return "/areas/list";
}
}
Comment From: snicoll
However, I am unsure if it's enough and I don't think I will try and get the whole thing running again.
It isn't I am afraid. Without a way for us to replicate the problem you've described, I am afraid this issue is no longer actionable. Thanks for the follow-up in any case!