Why do I get IllegalArgumentException Cannot convert value of type String to required type Product, in Spring?

StackOverflow https://stackoverflow.com/questions/4368828

Question

I receive the exception

Failed to convert property value of type [java.lang.String] to required type [beans.Product] for property product; nested exception is java.lang.IllegalArgumentException: Cannot convert value of type [java.lang.String] to required type [beans.Product] for property product: no matching editors or conversion strategy found

in the Errors errors object even before my DetailProductValidator starts validating through the validate method.

I don't understand why Spring does that. I don't have any input field that is mapped directly to the product property/object. I just use the product object's properties in the jsp. For example, I use:

<form:options items="${dpBackObj.product.colorMap}"/>
<!-- or -->
${dpBackObj.product.priceInDollars}

but I never use:

<form:input path="product"/>

Can anyone please explain why this happens? And maybe inform me of a simple solution?

The bean configuration for the controller is:

    <!-- DETAIL PRODUCT FORM CONTROLLER -->
<bean id="productDetailFormController" name="/detail.htm /addToCart.htm" 
        class="detailProduct.DetailProductFormController">
    <property name="sessionForm" value="true" />
    <property name="commandName" value="dpBackObj" />
    <property name="commandClass" value="detailProduct.DetailProductBackingObject" />
    <property name="validator">
        <bean class="detailProduct.DetailProductValidator" />
    </property>
    <property name="formView" value="detail" />
    <property name="successView" value="redirect:/viewCart.htm" />
    <property name="cartService" ref="cartServiceImpl"/>
</bean>

The backing object for the DetailProductFormController is:

public class DetailProductBackingObject {
    private String quantityOverflowError;
    private Product product;
    private int quantity;
    private ShoppingCart shoppingCart;
    private long sizeId;
    private long colorId;
    public DetailProductBackingObject() {
        this.product = new Product();
        this.sizeId = -1;
        this.colorId = -1;
    }
    //getters and setters
}

If you need some other info, I will provide. I am using Spring 2.5.5.

Kind Regards,
Despot

EDIT1 (due to request from axtavt):

<form:form method="post" commandName="dpBackObj">
<table width="730" border="0" cellspacing="0" cellpadding="0">
    <c:if test="${!empty dpBackObj.quantityOverflowError}">
        <tr>
            <td>
                <c:out value="${dpBackObj.quantityOverflowError}"/>
            </td>
        </tr>
    </c:if>
    <spring:bind path="dpBackObj.*">
        <c:if test="${not empty status.errorMessages}">
            <div class="val-summary text-error" id="errorDivId">
                <div style="" class="val-summary text-error" id="errorDivId">
                    <fmt:message key="detail.error.header"/>
                    <ul>
                        <c:forEach items="${status.errorMessages}" var="error">
                            <li><c:out value="${error}"/></li>
                        </c:forEach>
                    </ul>
                </div>
            </div>
        </c:if>
    </spring:bind>
    <tr>
        <td width="310" align="left" valign="top">
            <img src="${imagesPath}/${dpBackObj.product.largeImageUrl}" alt="${dpBackObj.product.description}" />
        </td>
        <td width="420" align="left" valign="top">
            <div id="tls_detPName">
                <c:out value="${dpBackObj.product.name}"></c:out>
            </div>
            <div >  
                <strong class="numeric">${dpBackObj.product.priceInDollars}</strong>
            </div>
            <div id="tls_detPDescLong">
                ${dpBackObj.product.largeDescription}
                <br />
            </div>
            <div >
                <table cellpadding="2" border="0">
                    <tr>
                        <td align="right">
                            <label for="p_sizes" class="label"><fmt:message key="viewCart.Size"/></label>
                        </td>
                        <td>
                            <form:select path="sizeId" > 
                                <form:option  value="-1" label="x"/> 
                                <form:options items="${dpBackObj.product.sizeMap}"/>
                            </form:select>
                        </td>
                    </tr>
                    <tr>
                        <td align="right">
                            <label for="p_colors" class="label"><fmt:message key="viewCart.Color"/></label>
                        </td>
                        <td>
                            <form:select path="colorId" > 
                                <form:option value="-1" label="y"/> 
                                <form:options items="${dpBackObj.product.colorMap}"/>
                            </form:select>
                        </td>
                    </tr>
                </table>
            </div>
            <div id="tls_addToCart">
                <div >
                    <label for="quantityId" class="label"><fmt:message key="viewCart.Quantity"/>:</label>
                    <form:input path="quantity" onkeypress="return checkForNumber(this, event)" maxlength="10" size="3" id="quantityId" cssClass="textbox-center"/>
                    <input type="image" name="addToCartButtonName" src="${imagesPath}/addToCartBtn.jpg" /> 
                </div>
            </div>
        </td>
    </tr>
</table>
</form:form>

EDIT2 (due to JacobM's request): This is my Validator:

public class DetailProductValidator implements Validator {
    public boolean supports(Class clazz) {
        return DetailProductBackingObject.class.equals(clazz);
    }

    public void validate(Object obj, Errors errors) {
        DetailProductBackingObject detailProductBackingObject = (DetailProductBackingObject) obj;
        if (detailProductBackingObject.getSizeId() == -1) {
            errors.rejectValue("sizeId", "error.detail.jsp.choose.size", null, "Input size.");
        }
    }
}

When I reach the line DetailProductBackingObject detailProductBackingObject = I already have the error.
The converting of the request parameters to the backing object properties happens in http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/web/servlet/mvc/BaseCommandController.html . This is what Spring says about the conversion:

Populating using request parameters and PropertyEditors: Upon receiving a request, any BaseCommandController will attempt to fill the command object using the request parameters. This is done using the typical and well-known JavaBeans property notation. When a request parameter named 'firstName' exists, the framework will attempt to call setFirstName([value]) passing the value of the parameter. Nested properties are of course supported. For instance a parameter named 'address.city' will result in a getAddress().setCity([value]) call on the command class.

It's important to realise that you are not limited to String arguments in your JavaBeans. Using the PropertyEditor-notion as supplied by the java.beans package, you will be able to transform Strings to Objects and the other way around. For instance setLocale(Locale loc) is perfectly possible for a request parameter named locale having a value of en, as long as you register the appropriate PropertyEditor in the Controller (see initBinder() for more information on that matter.

Validators: After the controller has successfully populated the command object with parameters from the request, it will use any configured validators to validate the object. Validation results will be put in a Errors object which can be used in a View to render any input problems.

Was it helpful?

Solution

Since I can't see anything wrong with the form, the only possible reason I can imagine is that you have a parameter named product in the URL of your form page.

If so, you can change your URLs or use DataBinder.setDisallowedFields() to disable the attempt to bind that parameter.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top