From spring
Build LDAP directory reads and writes in Spring with LdapTemplate, ODM entry mapping, LDAP repository queries, authentication checks, and embedded LDAP tests. Use this skill when building LDAP directory reads and writes in Spring with LdapTemplate, ODM entry mapping, LDAP repository queries, authentication checks, and embedded LDAP tests.
npx claudepluginhub ririnto/sinon --plugin springThis skill uses the workspace's default tool permissions.
Use this skill when building LDAP directory reads and writes in Spring with LdapTemplate, ODM entry mapping, LDAP repository queries, authentication checks, and embedded LDAP tests.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
Use this skill when building LDAP directory reads and writes in Spring with LdapTemplate, ODM entry mapping, LDAP repository queries, authentication checks, and embedded LDAP tests.
The latest released Spring LDAP line is 4.0.3. That 4.x line targets Spring Framework 7+, while Spring Boot 3.x still manages the parallel 3.x line, so keep the direct 4.0.3 path in this skill only when the project baseline already matches the 4.x generation. If the job needs LdapRepository, the current stable repository module is Spring Data LDAP 4.0.5, which depends on Spring LDAP 4.0.3.
Use spring-ldap for LDAP operations, ODM mapping, LDAP repository support, and embedded LDAP testing.
spring-security for authentication, authorization, and filter-chain design around LDAP authentication providers. Keep this skill focused on directory operations and data mapping.spring-security only when the real task is authentication or authorization policy rather than directory access itself.The ordinary Spring LDAP job is:
LdapTemplate for direct reads and writes, or add Spring Data LDAP and LdapRepository for repository-backed queries.DirContextAdapter.| Surface | Start here when | Open a reference when |
|---|---|---|
| Ordinary LDAP reads and writes | one service uses LdapTemplate or LdapRepository | stay in SKILL.md |
| Complex filters or DN parsing | filter escaping or DN extraction is the blocker | open references/filters-and-dn-handling.md |
| Context source tuning or compensating transactions | connection tuning or multi-write rollback semantics matter | open references/transactions-and-context-source.md |
| Advanced ODM and repository mapping | multi-valued attributes or raw @Query are the blocker | open references/advanced-odm-and-repositories.md |
| Embedded LDAP details | LDIF loading, embedded server choice, or schema-validation tuning is the blocker | open references/embedded-testing-and-ldif.md |
| LDAP-backed authentication | the real task is authentication or authorization policy | use the Spring Security LDAP path, not this skill |
| Situation | Use |
|---|---|
| Direct search or update logic | LdapTemplate |
| Repository-style query methods fit the schema | LdapRepository |
| Entry maps cleanly to a Java class | ODM annotations |
| Mapping is partial or highly dynamic | DirContextAdapter |
Prefer one mapping style per aggregate unless the schema forces a mixed approach.
Use direct Spring LDAP artifacts for the ordinary 4.0.3 path. Treat Spring Boot starter wiring as a parallel Boot-managed path only when the project is already on the matching Boot generation.
<dependencies>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
<version>4.0.3</version>
</dependency>
</dependencies>
<dependencies>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-ldap</artifactId>
<version>4.0.5</version>
</dependency>
</dependencies>
Use this direct path when repository-backed queries are required outside Boot-managed dependency alignment.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-ldap</artifactId>
</dependency>
</dependencies>
Use the Boot starter only when the project is already on a Boot line that manages the intended Spring LDAP generation.
Add the Spring LDAP test module when tests need LDAP-specific assertions or helpers. If the test path uses spring.ldap.embedded.*, also add an embedded LDAP server implementation for tests.
<dependencies>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
| Need | Artifact |
|---|---|
Ordinary LDAP access with LdapTemplate | spring-ldap-core |
Repository-backed LDAP queries with LdapRepository | spring-data-ldap plus spring-ldap-core |
| Boot-managed LDAP integration | spring-boot-starter-data-ldap |
| LDAP-specific test helpers | spring-ldap-test |
| Embedded test server implementation | unboundid-ldapsdk or another proven embedded LDAP server |
./mvnw test -Dtest=PersonRepositoryTest
./gradlew test --tests PersonRepositoryTest
Boot-managed property shape:
spring:
ldap:
embedded:
base-dn: dc=example,dc=com
validation:
enabled: false
data:
ldap:
repositories:
enabled: true
Disable validation only when the embedded server does not publish its schema to the JNDI provider.
Boot-managed property shape:
spring:
ldap:
urls: ldap://ldap.example.com:389
base: dc=example,dc=com
username: cn=admin,dc=example,dc=com
password: ${LDAP_ADMIN_PASSWORD}
Always externalize credentials. Use environment variables or secrets management, never hard-code passwords.
@Configuration
class LdapConfiguration {
@Bean
BaseLdapPathContextSource contextSource() {
LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl("ldap://ldap.example.com:389");
contextSource.setBase("dc=example,dc=com");
contextSource.setUserDn("cn=admin,dc=example,dc=com");
contextSource.setPassword(password);
contextSource.afterPropertiesSet();
return contextSource;
}
@Bean
LdapTemplate ldapTemplate(BaseLdapPathContextSource contextSource) {
return new LdapTemplate(contextSource);
}
}
Use this direct bean path as the ordinary 4.0.3 baseline when the project is not relying on Boot-managed LDAP configuration.
@Configuration
@EnableLdapRepositories(basePackages = "com.example.ldap")
class RepositoryConfiguration {
@Bean
BaseLdapPathContextSource contextSource() {
LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl("ldap://ldap.example.com:389");
contextSource.setBase("dc=example,dc=com");
contextSource.setUserDn("cn=admin,dc=example,dc=com");
contextSource.setPassword(password);
contextSource.afterPropertiesSet();
return contextSource;
}
}
Use this configuration only when the direct non-Boot path needs LdapRepository. If the task only needs LdapTemplate, keep the simpler direct bean path above.
LdapTemplate for direct operations or LdapRepository for query-method derivation.ContextSource with URL, base DN, user DN, and password before writing any query or mapping code.@Entry, @DnAttribute, @Attribute) when the directory schema maps cleanly to Java types. Use DirContextAdapter when mapping is dynamic or partial.LdapQueryBuilder or query().where(...) for readable filter chains. Treat raw string filters as a last resort and keep Spring LDAP builders or helpers in front of manual escaping.uid or cn, and keep the attribute name explicit in code and tests.AttributesMapper@Service
class PersonDirectory {
private final LdapTemplate ldap;
PersonDirectory(LdapTemplate ldap) {
this.ldap = ldap;
}
List<Person> findByLastName(String lastName) {
return ldap.search(
query().where("objectclass").is("person").and("sn").is(lastName),
(AttributesMapper<Person>) attrs -> {
Person p = new Person();
p.setCn((String) attrs.get("cn").get());
p.setSn((String) attrs.get("sn").get());
p.setMail((String) attrs.get("mail").get());
return p;
}
);
}
}
DirContextAdapter@Service
class PersonWriter {
private final LdapTemplate ldap;
PersonWriter(LdapTemplate ldap) {
this.ldap = ldap;
}
void updateMail(Name dn, String mail) {
DirContextOperations context = ldap.lookupContext(dn);
context.setAttributeValue("mail", mail);
ldap.modifyAttributes(context);
}
}
Use DirContextAdapter when the task needs direct write operations such as attribute updates and the schema does not justify a full ODM aggregate workflow.
@Entry(objectClasses = {"person"})
class Person {
@Id
private Name id;
@DnAttribute("cn")
private String commonName;
@Attribute("sn")
private String surname;
@Attribute("mail")
private String email;
@Attribute("telephoneNumber")
private String phone;
}
Use @DnAttribute for attributes that appear in the entry's relative distinguished name. Use @Attribute for other directory attributes.
interface PersonRepository extends LdapRepository<Person> {
List<Person> findBySurname(String surname);
List<Person> findByCommonNameContaining(String nameFragment);
}
Spring Data derives the filter from the method name. findBySurname generates (sn=<value>). findByCommonNameContaining generates (cn=*<value>*).
@Service
class LdapAuthService {
private final LdapTemplate ldap;
LdapAuthService(LdapTemplate ldap) {
this.ldap = ldap;
}
boolean checkCredentials(String uid, String password) {
try {
ldap.authenticate(
query().where("uid").is(uid).and("objectclass").is("person"),
password
);
return true;
} catch (Exception e) {
return false;
}
}
}
Use this pattern when you need a boolean result rather than an authenticated context. Replace uid with the actual login attribute used by the target directory schema.
@SpringBootTest
class PersonRepositoryTest {
@Autowired
PersonRepository repository;
@Test
void findBySurnameReturnsExpectedPerson() {
List<Person> results = repository.findBySurname("Doe");
assertAll(
() -> assertEquals(1, results.size()),
() -> assertEquals("jane.doe@example.com", results.get(0).getEmail())
);
}
}
Pair this test with embedded LDAP properties that point to a small LDIF fixture under src/test/resources/.
When the direct non-Boot path is the actual target, treat this example as a Boot-managed test slice and add an explicit repository configuration like the one above instead of assuming Boot auto-configuration.
spring:
ldap:
embedded:
base-dn: dc=example,dc=com
ldif: classpath:test-data.ldif
port: 0
Use a tiny LDIF fixture with only the entries needed by the test so query expectations stay obvious.
// Simple equality
query().where("objectclass").is("person")
// Compound filter
query().where("objectclass").is("person").and("sn").is("Doe")
// Substring match
query().where("cn").like("*Doe*")
// Presence check
query().where("telephoneNumber").isPresent()
@Entry(objectClasses = {"inetOrgPerson"})
class User {
@Id
private Name id;
@DnAttribute(value = "ou", index = 0)
private String organizationalUnit;
@DnAttribute(value = "cn", index = 1)
private String commonName;
}
The id field holds the full DN. @DnAttribute(index = N) extracts the N-th component from the DN path.
spring:
ldap:
urls: ldaps://ldap.example.com:636
base: dc=example,dc=com
username: cn=reader,dc=example,dc=com
password: ${LDAP_PASSWORD}
Use ldap:// for plain LDAP ports such as 389 and ldaps:// for LDAPS ports such as 636. Keep the scheme and port consistent.
LdapTemplate.authenticate() returns true for valid credentials and throws for invalid ones.Return:
DirContextAdapter is used@Query, or deeper repository derivation rules.