p:autoComplete itemLabel throws "The class 'java.lang.String' does not have the property 'label'."

You shouldn't feed it with List<SelectItem>. You should feed it with List<Pessoa>. You should also not concentrate on converting SelectItem. You should concentrate on converting the item value, which is Pessoa. The SelectItem is a leftover from the old JSF 1.x ages. In JSF 2.x this is not mandatory anymore, thanks to the var, itemValue and itemLabel attributes in the view. This keeps your bean clean from view-specific clutter.

The Converter is only necessary whenever you use itemValue="#{pessoa}" and the #{modeloPopupBuscaPessoa.itemSelecionado} refers a Pessoa property. You should then in getAsString() convert Pessoa to its unique String representation (so that it can be printed in HTML) and in getAsObject() convert from String to Pessoa (so that it can be set in bean property).

However, if #{pessoa.value} is a String and #{modeloPopupBuscaPessoa.itemSelecionado} is also a String, then you should just use itemValue="#{pessoa.value}" and remove the Converter altogether.

See also:

  • PrimeFaces showcase: <p:autoComplete> with POJO
  • Can I use omnifaces generic converter in primefaces autocomplete component?
  • Conversion Error setting value for 'null Converter'

A generic Converter which you can use for Primefaces Auto-completes and all other purposes:

import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.WeakHashMap;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.FacesConverter;

@FacesConverter(value = "entityConverter")
public class EntityConverter implements Converter {

    private static Map<Object, String> entities = new WeakHashMap<Object, String>();

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object entity) {
        synchronized (entities) {
            if (!entities.containsKey(entity)) {
                String uuid = UUID.randomUUID().toString();
                entities.put(entity, uuid);
                return uuid;
            } else {
                return entities.get(entity);
            }
        }
    }

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String uuid) {
        for (Entry<Object, String> entry : entities.entrySet()) {
            if (entry.getValue().equals(uuid)) {
                return entry.getKey();
            }
        }
        return null;
    }

}

I faced the same issue and the author's comment at Primefaces autocomplete with POJO and String value gave me the hint to find the source of the problem in my case.

Overview

The problem is that value=#{objectValue} is of type String but the method referenced in completeMethod is returning a List<Object>.

The Design

I have the following POJOs (simplified):

public class CollaboratorGroup {
   private String groupId;

   private String groupName;

   private Collaborator piUserId;

   ...
}

and

public class Collaborator {
   private String userId;

   private String fullName;

   private String groupId;
   ...
}

Does not matter whether this is a useful design. I just want to address the issue.

The following p:autoComplete (simplified):

<p:autoComplete var="group"
    itemLabel="#{group.groupId}"
    itemValue="#{group.groupId}"
    completeMethod="#{bean.completeGroup}"
    value="#{collaborator.groupId}">
    <f:facet name="itemtip">
        <p:panelGrid columns="2">
            <f:facet name="header">
                <h:outputText value="#{group.groupId}" />
            </f:facet>
            <h:outputText value="Name:" />
            <h:outputText value="#{group.groupName}" />
            <h:outputText value="PI" />
            <h:outputText value="#{group.piUserId.fullName}" />
        </p:panelGrid>
    </f:facet>
</p:autoComplete>

will throw The class 'java.lang.String' does not have the property 'groupId'. When I change to itemLabel=#{group}, I will see the groupId CG00255 in the input field but many of org.coadd.sharedresources.model.CollaboratorGroup@... in the dropdown list. If I select one of these, this toString() value is set to Collaborator.groupId which is not desired.

The Problem's Source

I feed the p:autoComplete with a List<CollaboratorGroup> while Collaborator.groupId is a String and itemLabel is used to "format" both, the String groupId set as value="#{collaborator.groupId}" and the CollaboratorGroup that comes from the List, generated by completeMethod="#{bean.completeGroup}".

Possible Solutions

  1. You could adjust the Model by changing the member groupId to CollaboratorGroup in Collaborator if it does not destroy your design. In this case, especially as CollaboratorGroup has the member Collaborator piUserId.
  2. You could just fill the p:autoComplete with List<String> groupIdList but in this case you have to find a different solution for the itemtip.

  3. A very quick solution is to use itemLabel="#{group.class.simpleName eq 'String' ? group : group.groupId}" as mentionned at Primefaces autocomplete with POJO and String value.

    • Problems
      • You have to care about NullPointerExceptions.
      • You fill up your View with logic.
      • It is not a very flexible or dynamic design.
  4. Implement 3. in a bean method itemLabel="#{bean.printGroupId(group)}" where you have full control over the logic. This is what I did.

    public String printGroupId(Object group) {
        if (group == null) return null;
        return (group instanceof String) ? (String) group : (group instanceof CollaboratorGroup) ? ((CollaboratorGroup) group).getGroupId() : null;
    }
    

    (Not the best, just to give you an idea.)