1. Introduction

Simply put, Spring uses property editors heavily for managing conversion between String values and custom Object types; this is based on Java Beans PropertyEditor.

In this tutorial, we’ll go over two different use cases for demonstrating automatic property editor binding and custom property editor binding.

2. Automatic Property Editor Binding

Standard JavaBeans infrastructure will automatically discover PropertyEditor classes if they are in the same package as the class they handle. Also, these need to have the same name as that class plus the Editor suffix.

For example, if we create a CreditCard model class, then we should name the editor class CreditCardEditor.

Let’s now go through a practical property binding example.

In our scenario, we’ll pass a credit card number as a path variable in the request URL, and we’ll bind that value as a CreditCard object.

Let’s first create the CreditCard model class defining fields rawCardNumber, Bank Identification Number (the first 6-digits), Account Number (digits from 7 to 15) and Check Code (last digit):

public class CreditCard {

    private String rawCardNumber;
    private Integer bankIdNo;
    private Integer accountNo;
    private Integer checkCode;

    // standard constructor, getters, setters
}

Next, we’ll create the CreditCardEditor class. This implements the business logic for converting the credit card number given as a String to a CreditCard object.

The property editor class should extend PropertyEditorSupport and implement the getAsText() and setAsText() methods:

public class CreditCardEditor extends PropertyEditorSupport {

    @Override
    public String getAsText() {
        CreditCard creditCard = (CreditCard) getValue();
        
        return creditCard == null ? "" : creditCard.getRawCardNumber();
    }
    
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        if (!StringUtils.hasLength(text)) {
            setValue(null);
        } else {
            CreditCard creditCard = new CreditCard();
            creditCard.setRawCardNumber(text);
            
            String cardNo = text.replaceAll("-", "");
            if (cardNo.length() != 16)
                throw new IllegalArgumentException(
                  "Credit card format should be xxxx-xxxx-xxxx-xxxx");
            
            try {
                creditCard.setBankIdNo( Integer.valueOf(cardNo.substring(0, 6)) );
                creditCard.setAccountNo( Integer.valueOf(
                  cardNo.substring(6, cardNo.length() - 1)) );
                creditCard.setCheckCode( Integer.valueOf(
                  cardNo.substring(cardNo.length() - 1)) );
            } catch (NumberFormatException nfe) {
                throw new IllegalArgumentException(nfe);
            }
            
            setValue(creditCard);
        }
    }
}

The getAsText() method is called when serializing an object to a String, while setAsText() is used to convert a String to another object.

Since these classes are located in the same package, we don’t need to do anything else for binding the Editor for type CreditCard.

We can now expose this as a Resource in a REST API; the operation takes a credit card number as a request path variable and Spring will bind that text value as a CrediCard object and pass it as a method argument:

@GetMapping(value = "/credit-card/{card-no}", 
  produces = MediaType.APPLICATION_JSON_VALUE)
public CreditCard parseCreditCardNumber(
    @PathVariable("card-no") CreditCard creditCard) {
    return creditCard;
}

For example, for a sample request URL /property-editor/credit-card/1234-1234-1111-0019, we’ll get the response:

{
    "rawCardNumber": "1234-1234-1111-0011",
    "bankIdNo": 123412,
    "accountNo": 341111001,
    "checkCode": 9
}

3. Custom Property Editor Binding

If we don’t have the required type class and the property editor class in the same package or with the expected naming conventions, we’ll have to define a custom binding between the required type and the property editor.

In our custom property editor binding scenario, a String value will be passed in the URL as path variable, and we’ll bind that value as an ExoticType object which merely keeps the value as an attribute.

As in section 2, let’s first create a model class ExoticType:

public class ExoticType {
    private String name;
    
    // standard constructor, getters, setters
}

And our custom property editor class CustomExoticTypeEditor which again extends PropertyEditorSupport: 

public class CustomExoticTypeEditor extends PropertyEditorSupport {

    @Override
    public String getAsText() {
        ExoticType exoticType = (ExoticType) getValue();
        return exoticType == null ? "" : exoticType.getName();
    }
    
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        ExoticType exoticType = new ExoticType();
        exoticType.setName(text.toUpperCase());
        
        setValue(exoticType);
    }
}

Since Spring can’t detect the property editor, we’ll need a method annotated with @InitBinder in our Controller class that registers the editor:

@InitBinder
public void initBinder(WebDataBinder binder) {
    binder.registerCustomEditor(ExoticType.class, 
        new CustomExoticTypeEditor());
}

Then we can bind the user input to ExoticType object:

@GetMapping(
  value = "/exotic-type/{value}", 
  produces = MediaType.APPLICATION_JSON_VALUE)
public ExoticType parseExoticType(
  @PathVariable("value") ExoticType exoticType) {
    return exoticType;
}

For the sample request URL /property-editor/exotic-type/passion-fruit, we’ll get the sample response:

{
    "name": "PASSION-FRUIT"
}

4. Conclusion

In this quick article, we saw how we could use automatic and custom property editor binding to convert human-readable String values to complex Java types.

The full source code of our examples here is, as always, over on GitHub.