How create a custom converter in JSF 2?

You’re trying to pass a complex object around as HTTP request parameter which can be only be a String. JSF/EL has builtin converters for primitives and its wrappers (e.g. int, Integer) and even enums. But for all other types you really need to write a custom converter. In this case you need to write a converter which converts between String and Operation. The String is then used as option value (open page in browser, rightclick and View Source and notice the <option value>). The Operation is then used as model value. The String should uniquely identify the Operation object. You could use operation ID for this.

But in this particular case, with such a hardcoded map and a relatively simple model, I think it’s easier to use an enum instead.

public enum Operation {

    DONATION("Donation"), EXCHANGE("Exchange");

    private String label;

    private Operation(String label) {
        this.label = label;
    }

    public string getLabel() {
        return label;
    }

}

with

private Operation operation; // +getter +setter

public Operation[] getOperations() {
    return Operation.values();
}

and

<h:selectOneMenu value="#{bean.operation}">
    <f:selectItems value="#{bean.operations}" var="operation" itemValue="#{operation}" itemLabel="#{operation.label}" />
</h:selectOneMenu>

But if those values have actually to be retrieved from the DB and its size is undefinied, then you still really need a custom converter. You could in getAsString() return the ID and in getAsObject() use the operation DAO/EJB to get an Operation by the ID.

@ManagedBean
@RequestScoped
public class OperationConverter implements Converter {

    @EJB
    private OperationService operationService;

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        // Convert here Operation object to String value for use in HTML.
        if (!(value instanceof Operation) || ((Operation) value).getId() == null) {
            return null;
        }

        return String.valueOf(((Operation) value).getId());
    }

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        // Convert here String submitted value to Operation object.
        if (value == null || !value.matches("\\d+")) {
            return null;
        }

        Operation operation = operationService.find(Long.valueOf(value));

        if (operation == null) {
            throw new ConverterException(new FacesMessage("Unknown operation ID: " + value));
        }

        return operation;
    }

}

Use it as follows:

<h:selectOneMenu ... converter="#{operationConverter}">

As to why it’s a @ManagedBean instead of @FacesConverter, read this: Converting and validating GET request parameters.


Update as to the Validation Error: value not valid error, this means that the equals() method of the Operation class is broken or missing. During validation, JSF compares the submitted value with the list of available values by Object#equals(). If no one of the list matches with the submitted value, then you’ll see this error. So, ensure that equals() is properly implemented. Here’s a basic example which compares by the DB technical identity.

public boolean equals(Object other) {
    return (other instanceof Operation) && (id != null) 
         ? id.equals(((Operation) other).id) 
         : (other == this);
}

Don’t forget to implement hashCode() as well:

public int hashCode() {
    return (id != null) 
         ? (getClass().hashCode() + id.hashCode())
         : super.hashCode();
}

Leave a Comment