title: Spring LDAP using Java Configuration
body: <p>I was following the <a href="https://github.com/spring-projects/spring-ldap/tree/master/samples" rel="noreferrer">samples of Spring LDAP project</a> and was trying to convert <code>xml</code> configuration to <code>Java Configuration</code>.</p>
  
  <blockquote>
    <p>I am trying to do CRUD operations on the LDAP server.</p>
  </blockquote>
  
  <p>I was able to figure out the following, </p>
  
  <p>This is the xml configuration for the application, which I am hoping to convert into Java Config.</p>
  
  <p>
  </p>
  
  <pre><code>&lt;context:property-placeholder location="classpath:/ldap.properties"
          system-properties-mode="OVERRIDE" /&gt;
      &lt;context:annotation-config /&gt;
  
      &lt;ldap:context-source id="contextSource" password="${sample.ldap.password}"
          url="${sample.ldap.url}" username="${sample.ldap.userDn}" base="${sample.ldap.base}" /&gt;
  
      &lt;ldap:ldap-template id="ldapTemplate"
          context-source-ref="contextSource" /&gt;
  
      &lt;!-- This will scan the org.springframework.ldap.samples.useradmin.domain 
          package for interfaces extending CrudRepository (in our case, LdapRepository), 
          automatically creating repository beans based on these interfaces. --&gt;
      &lt;ldap:repositories base-package="com.cazysystems.appstore.model" /&gt;
  
      &lt;!-- This one will never be referenced directly, but the ldap:repositories 
          tag will make sure it will be 'wired in', because the GroupRepo interface 
          extends from an interface that GroupRepoImpl imlements. --&gt;
      &lt;bean class="com.cazysystems.appstore.model.impli.GroupRepoImpl" /&gt;
  
      &lt;bean class="com.cazysystems.appstore.model.impli.DepartmentRepoImpl" /&gt;
  
      &lt;bean class="com.cazysystems.appstore.service.UserService"&gt;
          &lt;property name="directoryType" value="${sample.ldap.directory.type}" /&gt;
      &lt;/bean&gt;
  </code></pre>
  
  <p>So I have the following classes that are supposed to do the migration,</p>
  
  <pre><code>@Configuration
  @EnableLdapRepositories("com.cazysystems.appstore.model")
  public class LdapConfiguration {
  
      @Autowired
      Environment env;
  
      @Bean
      public LdapContextSource contextSource() {
          LdapContextSource contextSource = new LdapContextSource();
          contextSource.setUrl(env.getRequiredProperty("sample.ldap.url"));
          contextSource.setBase(env.getRequiredProperty("sample.ldap.base"));
          contextSource.setUserDn(env.getRequiredProperty("sample.ldap.userDn"));
          contextSource.setPassword(env
                  .getRequiredProperty("sample.ldap.password"));
          return contextSource;
      }
  
      @Bean
      public LdapTemplate ldapTemplate() {
          return new LdapTemplate(contextSource());
      }
  
  }
  </code></pre>
  
  <p>and the </p>
  
  <pre><code> @Configuration
  public class AuthenticationConfiguration extends
          GlobalAuthenticationConfigurerAdapter {
  
      @Autowired
      Environment env;
  
      @Override
      public void init(AuthenticationManagerBuilder auth) throws Exception {
  
          auth.ldapAuthentication()
                  // .userDetailsContextMapper(userDetailsContextMapper())
                  .userDnPatterns(
                          env.getRequiredProperty("ldap.user_dn_patterns"))
                  .groupSearchBase(
                          env.getRequiredProperty("ldap.group_search_base"))
                  .contextSource().ldif("classpath:setup_data.ldif")
                  .url(env.getRequiredProperty("sample.ldap.url"))
                  .managerDn("sample.ldap.userDn")
                  .managerPassword("sample.ldap.password").port(10389);
      }
  
  }
  </code></pre>
  
  <p>But when I run the application,</p>
  
  <p>I get the following error,</p>
  
  <pre><code>Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'groupRepo': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Not an managed type: class com.cazysystems.appstore.model.Group
  </code></pre>
  
  <p>But under the <code>com.cazysystems.appstore.model</code>package, </p>
  
  <p>I have,</p>
  
  <pre><code>public interface GroupRepo extends LdapRepository&lt;Group&gt;, GroupRepoExtension {
      public final static String USER_GROUP = "ROLE_USER";
  
      Group findByName(String groupName);
  
      @Query("(member={0})")
      Collection&lt;Group&gt; findByMember(Name member);
  }
  </code></pre>
  
  <p>and</p>
  
  <pre><code>@Entry(objectClasses = { "groupOfNames", "top" }, base = "ou=Groups")
  public final class Group {
      @Id
      private Name id;
  
      @Attribute(name = "cn")
      @DnAttribute(value = "cn", index = 1)
      private String name;
  
      @Attribute(name = "description")
      private String description;
  
      @Attribute(name = "member")
      private Set&lt;Name&gt; members = new HashSet&lt;Name&gt;();
  
      public String getDescription() {
          return description;
      }
  
      public void setDescription(String description) {
          this.description = description;
      }
  
      public Set&lt;Name&gt; getMembers() {
          return members;
      }
  
      public void addMember(Name newMember) {
          members.add(newMember);
      }
  
      public void removeMember(Name member) {
          members.remove(member);
      }
  
      public Name getId() {
          return id;
      }
  
      public void setId(Name id) {
          this.id = id;
      }
  
      public String getName() {
          return name;
      }
  
      public void setName(String name) {
          this.name = name;
      }
  }
  </code></pre>
  
  <p>But this annotation is supposed to do the trick, But it is not working,</p>
  
  <pre><code>@EnableLdapRepositories("com.cazysystems.appstore.model")
  </code></pre>
  
  <p>I am using the following dependencies in my pom.</p>
  
  <pre><code>&lt;dependency&gt;
          &lt;groupId&gt;org.springframework.security&lt;/groupId&gt;
          &lt;artifactId&gt;spring-security-ldap&lt;/artifactId&gt;
      &lt;/dependency&gt;
  
  
      &lt;dependency&gt;
          &lt;groupId&gt;org.springframework.ldap&lt;/groupId&gt;
          &lt;artifactId&gt;spring-ldap-core&lt;/artifactId&gt;
          &lt;version&gt;2.0.3.RELEASE&lt;/version&gt;
      &lt;/dependency&gt;
  
      &lt;dependency&gt;
          &lt;groupId&gt;org.springframework.ldap&lt;/groupId&gt;
          &lt;artifactId&gt;spring-ldap-core-tiger&lt;/artifactId&gt;
          &lt;version&gt;2.0.3.RELEASE&lt;/version&gt;
      &lt;/dependency&gt;
  
      &lt;dependency&gt;
          &lt;groupId&gt;org.springframework.data&lt;/groupId&gt;
          &lt;artifactId&gt;spring-data-commons&lt;/artifactId&gt;
      &lt;/dependency&gt;
  </code></pre>
  
  <p>I am not sure what I am doing wrong, because the documentation on this are very hard to find. Please help if you know this stuff.</p>
  
  <p>EDIT:</p>
  
  <p>Here is my Controller class,</p>
  
  <pre><code>@Controller
  public class GroupController {
  
      @Autowired
      private GroupRepo groupRepo;
  
      @Autowired
      private UserService userService;
  
      @RequestMapping(value = "/groups", method = GET)
      public String listGroups(ModelMap map) {
          map.put("groups", groupRepo.getAllGroupNames());
          return "listGroups";
      }
  
      @RequestMapping(value = "/newGroup", method = GET)
      public String initNewGroup() {
          return "newGroup";
      }
  
      @RequestMapping(value = "/groups", method = POST)
      public String newGroup(Group group) {
          groupRepo.create(group);
  
          return "redirect:groups/" + group.getName();
      }
  
      @RequestMapping(value = "/groups/{name}", method = GET)
      public String editGroup(@PathVariable String name, ModelMap map) {
          Group foundGroup = groupRepo.findByName(name);
          map.put("group", foundGroup);
  
          final Set&lt;User&gt; groupMembers = userService.findAllMembers(foundGroup.getMembers());
          map.put("members", groupMembers);
  
          Iterable&lt;User&gt; otherUsers = Iterables.filter(userService.findAll(), new Predicate&lt;User&gt;() {
              @Override
              public boolean apply(User user) {
                  return !groupMembers.contains(user);
              }
          });
          map.put("nonMembers", Lists.newLinkedList(otherUsers));
  
          return "editGroup";
      }
  
      @RequestMapping(value = "/groups/{name}/members", method = POST)
      public String addUserToGroup(@PathVariable String name, @RequestParam String userId) {
          Group group = groupRepo.findByName(name);
          group.addMember(userService.toAbsoluteDn(LdapUtils.newLdapName(userId)));
  
          groupRepo.save(group);
  
          return "redirect:/groups/" + name;
      }
  
      @RequestMapping(value = "/groups/{name}/members", method = DELETE)
      public String removeUserFromGroup(@PathVariable String name, @RequestParam String userId) {
          Group group = groupRepo.findByName(name);
          group.removeMember(userService.toAbsoluteDn(LdapUtils.newLdapName(userId)));
  
          groupRepo.save(group);
  
          return "redirect:/groups/" + name;
      }
  }
  </code></pre>
  
  <p>EDIT:</p>
  
  <p>The following is the complete stack trace,</p>
  
  <pre><code>org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'groupController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.cazysystems.appstore.domain.GroupRepo com.eazysystems.appstore.controller.GroupController.groupRepo; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'groupRepo': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Not an managed type: class com.cazysystems.appstore.domain.Group
      at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:334)
      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1210)
      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
      at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
      at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
      at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
      at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
      at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:755)
      at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:757)
      at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480)
      at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:118)
      at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:686)
      at org.springframework.boot.SpringApplication.run(SpringApplication.java:320)
      at org.springframework.boot.SpringApplication.run(SpringApplication.java:957)
      at org.springframework.boot.SpringApplication.run(SpringApplication.java:946)
      at com.eazysystems.appstore.Application.main(Application.java:20)
  Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.cazysystems.appstore.domain.GroupRepo com.eazysystems.appstore.controller.GroupController.groupRepo; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'groupRepo': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Not an managed type: class com.eazysystems.appstore.domain.Group
      at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:561)
      at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
      at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:331)
      ... 16 common frames omitted
  Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'groupRepo': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Not an managed type: class com.eazysystems.appstore.domain.Group
      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574)
      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539)
      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
      at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
      at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
      at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
      at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
      at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1120)
      at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1044)
      at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
      at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:533)
      ... 18 common frames omitted
  Caused by: java.lang.IllegalArgumentException: Not an managed type: class com.cazysystems.appstore.domain.Group
      at org.hibernate.jpa.internal.metamodel.MetamodelImpl.managedType(MetamodelImpl.java:219)
      at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.&lt;init&gt;(JpaMetamodelEntityInformation.java:68)
      at org.springframework.data.jpa.repository.support.JpaEntityInformationSupport.getMetadata(JpaEntityInformationSupport.java:67)
      at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getEntityInformation(JpaRepositoryFactory.java:145)
      at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:89)
      at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:69)
      at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:173)
      at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.initAndReturn(RepositoryFactoryBeanSupport.java:239)
      at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:225)
      at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:92)
      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1633)
      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1570)
      ... 28 common frames omitted
  </code></pre>
  
  <p>My UserService class as following,</p>
  
  <pre><code>@Component
  public class UserService implements BaseLdapNameAware {
  
      @Autowired
      private UserRepo userRepo;
  
      @Autowired
      private GroupRepo groupRepo;
  
      private LdapName baseLdapPath;
  
      @Autowired
      @Value("${sample.ldap.directory.type}")
      private DirectoryType directoryType;
  
      /*
       * @Autowired public UserService(UserRepo userRepo, GroupRepo groupRepo) {
       * this.userRepo = userRepo; this.groupRepo = groupRepo; }
       */
      public Group getUserGroup() {
          return groupRepo.findByName(GroupRepo.USER_GROUP);
      }
  
      public void setDirectoryType(DirectoryType directoryType) {
          this.directoryType = directoryType;
      }
  
      @Override
      public void setBaseLdapPath(LdapName baseLdapPath) {
          this.baseLdapPath = baseLdapPath;
      }
  
      public Iterable&lt;User&gt; findAll() {
          return userRepo.findAll();
      }
  
      public User findUser(String userId) {
          return userRepo.findOne(LdapUtils.newLdapName(userId));
      }
  
      public User createUser(User user) {
          User savedUser = userRepo.save(user);
  
          Group userGroup = getUserGroup();
  
          // The DN the member attribute must be absolute
          userGroup.addMember(toAbsoluteDn(savedUser.getId()));
          groupRepo.save(userGroup);
  
          return savedUser;
      }
  
      public LdapName toAbsoluteDn(Name relativeName) {
          return LdapNameBuilder.newInstance(baseLdapPath).add(relativeName)
                  .build();
      }
  
      /**
       * This method expects absolute DNs of group members. In order to find the
       * actual users the DNs need to have the base LDAP path removed.
       *
       * @param absoluteIds
       * @return
       */
      public Set&lt;User&gt; findAllMembers(Iterable&lt;Name&gt; absoluteIds) {
          return Sets.newLinkedHashSet(userRepo
                  .findAll(toRelativeIds(absoluteIds)));
      }
  
      public Iterable&lt;Name&gt; toRelativeIds(Iterable&lt;Name&gt; absoluteIds) {
          return Iterables.transform(absoluteIds, new Function&lt;Name, Name&gt;() {
              @Override
              public Name apply(Name input) {
                  return LdapUtils.removeFirst(input, baseLdapPath);
              }
          });
      }
  
      public User updateUser(String userId, User user) {
          LdapName originalId = LdapUtils.newLdapName(userId);
          User existingUser = userRepo.findOne(originalId);
  
          existingUser.setFirstName(user.getFirstName());
          existingUser.setLastName(user.getLastName());
          existingUser.setFullName(user.getFullName());
          existingUser.setEmail(user.getEmail());
          existingUser.setPhone(user.getPhone());
          existingUser.setTitle(user.getTitle());
          existingUser.setDepartment(user.getDepartment());
          existingUser.setUnit(user.getUnit());
  
          if (directoryType == DirectoryType.AD) {
              return updateUserAd(originalId, existingUser);
          } else {
              return updateUserStandard(originalId, existingUser);
          }
      }
  
      /**
       * Update the user and - if its id changed - update all group references to
       * the user.
       *
       * @param originalId
       *            the original id of the user.
       * @param existingUser
       *            the user, populated with new data
       *
       * @return the updated entry
       */
      private User updateUserStandard(LdapName originalId, User existingUser) {
          User savedUser = userRepo.save(existingUser);
  
          if (!originalId.equals(savedUser.getId())) {
              // The user has moved - we need to update group references.
              LdapName oldMemberDn = toAbsoluteDn(originalId);
              LdapName newMemberDn = toAbsoluteDn(savedUser.getId());
  
              Collection&lt;Group&gt; groups = groupRepo.findByMember(oldMemberDn);
              updateGroupReferences(groups, oldMemberDn, newMemberDn);
          }
          return savedUser;
      }
  
      /**
       * Special behaviour in AD forces us to get the group membership before the
       * user is updated, because AD clears group membership for removed entries,
       * which means that once the user is update we've lost track of which groups
       * the user was originally member of, preventing us to update the membership
       * references so that they point to the new DN of the user.
       *
       * This is slightly less efficient, since we need to get the group
       * membership for all updates even though the user may not have been moved.
       * Using our knowledge of which attributes are part of the distinguished
       * name we can do this more efficiently if we are implementing specifically
       * for Active Directory - this approach is just to highlight this quite
       * significant difference.
       *
       * @param originalId
       *            the original id of the user.
       * @param existingUser
       *            the user, populated with new data
       *
       * @return the updated entry
       */
      private User updateUserAd(LdapName originalId, User existingUser) {
          LdapName oldMemberDn = toAbsoluteDn(originalId);
          Collection&lt;Group&gt; groups = groupRepo.findByMember(oldMemberDn);
  
          User savedUser = userRepo.save(existingUser);
          LdapName newMemberDn = toAbsoluteDn(savedUser.getId());
  
          if (!originalId.equals(savedUser.getId())) {
              // The user has moved - we need to update group references.
              updateGroupReferences(groups, oldMemberDn, newMemberDn);
          }
          return savedUser;
      }
  
      private void updateGroupReferences(Collection&lt;Group&gt; groups,
              Name originalId, Name newId) {
          for (Group group : groups) {
              group.removeMember(originalId);
              group.addMember(newId);
  
              groupRepo.save(group);
          }
      }
  
      public List&lt;User&gt; searchByNameName(String lastName) {
          return userRepo.findByFullNameContains(lastName);
      }
  }
  </code></pre>
  
  <p>During the trouble shooting,</p>
  
  <p>I noticed that, the application fails only when I used both JPA and LDAP together. When I removed all the JPA dependencies and implementations, the application works as expected.</p>
  
  <p>The issue seems to be caused by the conflict between JPA and LDAP dependencies. But I am not sure how to resolve it. </p>