<p dir="auto"><strong><a href="https://jira.spring.io/secure/ViewProfile.jspa?name=egervari" rel="nofollow">Ken Egervari</a></strong> opened <strong><a href="https://jira.spring.io/browse/SPR-8665?redirect=false" rel="nofollow">SPR-8665</a></strong> and commented</p>
<p dir="auto">I have an application that I've recently upgraded to 3.0.6 from 3.0.5. The application used a property editor to convert a String to a List of objects, back and forth.</p>
<p dir="auto">I am positive this code used to work. In fact, the unit tests for the property editor continue to work as expected.</p>
<p dir="auto">However, whenever a controller uses the property editor, it wraps the List of objects with ANOTHER list, which is absolutely not what we want to happen. Did they change how property editors work? How do I fix it?</p>
<p dir="auto">My init binder method looks like this:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="@InitBinder
public void initBinder(WebDataBinder binder) {
     binder.registerCustomEditor(List.class, new UsernamesToRecipientsEditor(userAccountDao));
}"><pre class="notranslate"><code class="notranslate">@InitBinder
public void initBinder(WebDataBinder binder) {
     binder.registerCustomEditor(List.class, new UsernamesToRecipientsEditor(userAccountDao));
}
</code></pre></div>
<p dir="auto">Because java is weakly typed when it comes to List, I want you to know that this is really a <code class="notranslate">List&lt;Recipient&gt;</code> and not just a List of any random object. Here is the property editor code:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="public class UsernamesToRecipientsEditor extends PropertyEditorSupport {
         private UserAccountDao dao;
         public UsernamesToRecipientsEditor(UserAccountDao dao) {
                  this.dao = dao;
         }
         @Override
         public String getAsText() {
                  Object obj = getValue();
                  if(obj == null) {
                           return &quot;&quot;;
                  }
                  if(obj instanceof List) {
                           List&lt;Recipient&gt; recipients = (List&lt;Recipient&gt;) obj;
                           StringBuilder builder = new StringBuilder();
                           for(Iterator&lt;Recipient&gt; iterator = recipients.iterator(); iterator.hasNext();) {
                                    Recipient recipient = iterator.next();
                                    builder.append(recipient.getUserAccount().getUsername());
                                    if(iterator.hasNext()) {
                                                 builder.append(&quot;, &quot;);
                                    }
                           }
                           return builder.toString();
                  }
                  throw new IllegalArgumentException(&quot;Value must be a UserAccount&quot;);
         }
         @Override
         public void setAsText(String text) {
                  List&lt;Recipient&gt; recipients = new ArrayList&lt;Recipient&gt;();
                  if(!StringUtils.hasText(text)) {
                           setValue(recipients);
                           return;
                  }
                  for(String username : StringUtils.commaDelimitedListToSet(text)) {
                           UserAccount userAccount = dao.findByUsername(username.trim());
                           if(userAccount != null) {
                                    Recipient recipient = new Recipient(userAccount);
                                    if(!containsRecipient(recipients, recipient)) {
                                                 recipients.add(recipient);
                                    }
                           }
                  }
                  setValue(recipients);
         }
         private boolean containsRecipient(List&lt;Recipient&gt; recipients, Recipient recipient) {
                  for(Recipient r : recipients) {
                           if(r.getUserAccount().getId() == recipient.getUserAccount().getId()) return true;
                  }
                  return false;
         }
}"><pre class="notranslate"><code class="notranslate">public class UsernamesToRecipientsEditor extends PropertyEditorSupport {
         private UserAccountDao dao;
         public UsernamesToRecipientsEditor(UserAccountDao dao) {
                  this.dao = dao;
         }
         @Override
         public String getAsText() {
                  Object obj = getValue();
                  if(obj == null) {
                           return "";
                  }
                  if(obj instanceof List) {
                           List&lt;Recipient&gt; recipients = (List&lt;Recipient&gt;) obj;
                           StringBuilder builder = new StringBuilder();
                           for(Iterator&lt;Recipient&gt; iterator = recipients.iterator(); iterator.hasNext();) {
                                    Recipient recipient = iterator.next();
                                    builder.append(recipient.getUserAccount().getUsername());
                                    if(iterator.hasNext()) {
                                                 builder.append(", ");
                                    }
                           }
                           return builder.toString();
                  }
                  throw new IllegalArgumentException("Value must be a UserAccount");
         }
         @Override
         public void setAsText(String text) {
                  List&lt;Recipient&gt; recipients = new ArrayList&lt;Recipient&gt;();
                  if(!StringUtils.hasText(text)) {
                           setValue(recipients);
                           return;
                  }
                  for(String username : StringUtils.commaDelimitedListToSet(text)) {
                           UserAccount userAccount = dao.findByUsername(username.trim());
                           if(userAccount != null) {
                                    Recipient recipient = new Recipient(userAccount);
                                    if(!containsRecipient(recipients, recipient)) {
                                                 recipients.add(recipient);
                                    }
                           }
                  }
                  setValue(recipients);
         }
         private boolean containsRecipient(List&lt;Recipient&gt; recipients, Recipient recipient) {
                  for(Recipient r : recipients) {
                           if(r.getUserAccount().getId() == recipient.getUserAccount().getId()) return true;
                  }
                  return false;
         }
}
</code></pre></div>
<p dir="auto">Basically what this code does is it converts a string containing a bunch of username's separated by commas and converts them to the actual user objects, back and forth between them. It works in unit tests, and this worked in the past.</p>
<p dir="auto">Unfortunately, after Spring MVC calls into this property editor, it wraps the result from <code class="notranslate">setValue(recipients)</code> inside of ANOTHER List... so the end result is a "List of a List of Recipients". This isn't what I want.</p>
<p dir="auto">Since this code above USED TO work, I have to conclude that some bug or behavioral change has happened. Please help.</p>
<hr>
<p dir="auto"><strong>Affects:</strong> 3.0.6</p>
<p dir="auto"><strong>Reference URL:</strong> <a href="http://stackoverflow.com/questions/7293928/did-spring-framework-3-0-6-break-property-editor-support-for-lists" rel="nofollow">http://stackoverflow.com/questions/7293928/did-spring-framework-3-0-6-break-property-editor-support-for-lists</a></p>
<p dir="auto"><strong>Issue Links:</strong></p>
<ul dir="auto">
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="398114408" data-permission-text="Title is private" data-url="https://github.com/spring-projects/spring-framework/issues/13316" data-hovercard-type="issue" data-hovercard-url="/spring-projects/spring-framework/issues/13316/hovercard" href="https://github.com/spring-projects/spring-framework/issues/13316">#13316</a> Collection Binding broken - TypeConverterDelegate does bind Collection Elements to Collection type instead of the collection elements Type (<em><strong>"duplicates"</strong></em>)</li>
</ul>
<p dir="auto"><strong><a href="https://jira.spring.io/secure/ViewProfile.jspa?name=marvel_ss" rel="nofollow">Marvel</a></strong> opened <strong><a href="https://jira.spring.io/browse/SPR-5213?redirect=false" rel="nofollow">SPR-5213</a></strong> and commented</p>
<p dir="auto">I saw a similar issue raised out by Jasper Rosenberg   (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="398082304" data-permission-text="Title is private" data-url="https://github.com/spring-projects/spring-framework/issues/8738" data-hovercard-type="issue" data-hovercard-url="/spring-projects/spring-framework/issues/8738/hovercard" href="https://github.com/spring-projects/spring-framework/issues/8738">#8738</a> ).</p>
<p dir="auto">I would like to share my understanding on this , code snippet below is using the non-thread safe variable beanClass, which may cause issue in multiple thread environment.</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="               private Object beanClass

public String getBeanClassName() {
	if (this.beanClass instanceof Class) {
		return ((Class) this.beanClass).getName();
	}
	else {
		return (String) this.beanClass;
	}
}



public Class resolveBeanClass(ClassLoader classLoader) throws ClassNotFoundException {
                 //This may cause issue related to synchronized
                                   if (this.beanClass == null) {
		return null;
	}
	Class resolvedClass = ClassUtils.forName(getBeanClassName(), classLoader);
	this.beanClass = resolvedClass;
	return resolvedClass;
}"><pre class="notranslate"><code class="notranslate">               private Object beanClass

public String getBeanClassName() {
	if (this.beanClass instanceof Class) {
		return ((Class) this.beanClass).getName();
	}
	else {
		return (String) this.beanClass;
	}
}



public Class resolveBeanClass(ClassLoader classLoader) throws ClassNotFoundException {
                 //This may cause issue related to synchronized
                                   if (this.beanClass == null) {
		return null;
	}
	Class resolvedClass = ClassUtils.forName(getBeanClassName(), classLoader);
	this.beanClass = resolvedClass;
	return resolvedClass;
}
</code></pre></div>
<p dir="auto">Please let me know if this is fixed in higher version of Spring.</p>
<hr>
<p dir="auto"><strong>Affects:</strong> 2.0.3</p>
<p dir="auto"><strong>Attachments:</strong></p>
<ul dir="auto">
<li><a href="https://jira.spring.io/secure/attachment/14768/issue.bmp" rel="nofollow">issue.bmp</a> (<em>8.67 kB</em>)</li>
</ul>
<p dir="auto"><strong>Issue Links:</strong></p>
<ul dir="auto">
<li><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="398091332" data-permission-text="Title is private" data-url="https://github.com/spring-projects/spring-framework/issues/9885" data-hovercard-type="issue" data-hovercard-url="/spring-projects/spring-framework/issues/9885/hovercard" href="https://github.com/spring-projects/spring-framework/issues/9885">#9885</a> Class cast exception in AbstractBeanDefinition.getBeanClassName(AbstractBeanDefinition.java:305) (<em><strong>"is duplicated by"</strong></em>)</li>
</ul>
<p dir="auto"><strong>Backported to:</strong> <a href="https://github.com/spring-projects/spring-framework/milestone/45?closed=1">2.0.8</a></p>