name: spring-ldap
description: >-
Build LDAP directory reads and writes in Spring with LdapTemplate, ODM entry mapping, LDAP repository queries, authentication checks, and embedded LDAP tests.
Use when querying directory entries with filters, mapping LDAP attributes to Java objects with @Entry, or setting up embedded UnboundID test servers.
Spring LDAP
The latest released Spring LDAP line is 4.1.0. 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.1.0 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.1.0, which depends on Spring LDAP 4.1.0.
Boundaries
Use spring-ldap for LDAP operations, ODM mapping, LDAP repository support, and embedded LDAP testing.
- Authentication, authorization, and filter-chain design around LDAP authentication providers are Spring Security concerns outside this directory-operations scope.
- Keep repository-backed LDAP query design in this skill.
- Treat authentication or authorization policy as a separate application-security concern rather than directory access itself.
Common path
The ordinary Spring LDAP job is:
- Add the smallest dependency set that covers the LDAP operation type.
- Configure the LDAP context source with server URL, base DN, and credentials.
- Use
LdapTemplatefor direct reads and writes, or add Spring Data LDAP andLdapRepositoryfor repository-backed queries. - Map directory entries with ODM annotations or
DirContextAdapter. - Add an embedded LDAP test that proves queries return the expected entries.
Surface map
| 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 LDAPS/SSL, 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 | outside this directory access and mapping scope |
Mapping decisions
| Situation | Use |
|---|---|
| Direct search or update logic without ODM | LdapClient |
| Direct search or update logic with ODM | 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.
Dependency baseline
Use direct Spring LDAP artifacts for the ordinary 4.1.0 path. Treat Spring Boot starter wiring as a parallel Boot-managed path only when the project is already on the matching Boot generation.
Runtime baseline
<dependencies>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
<version>4.1.0</version>
</dependency>
</dependencies>
Repository-backed direct path
<dependencies>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-ldap</artifactId>
<version>4.1.0</version>
</dependency>
</dependencies>
Use this direct path when repository-backed queries are required outside Boot-managed dependency alignment.
Boot-managed alternative
<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.
Test-only additions
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>
Feature-to-artifact map
| Need | Artifact |
|---|---|
Ordinary LDAP access with LdapTemplate or LdapClient |
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 |
First safe configuration
First safe commands
./mvnw test -Dtest=PersonRepositoryTest
./gradlew test --tests PersonRepositoryTest
Embedded LDAP test configuration
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.
Production context source properties
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.
Direct Spring LDAP context source shape
@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);
}
@Bean
LdapClient ldapClient(BaseLdapPathContextSource contextSource) {
return LdapClient.create(contextSource);
}
}
Use this direct bean path as the ordinary 4.1.0 baseline when the project is not relying on Boot-managed LDAP configuration.
Direct repository configuration shape
@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.
Coding procedure
- Identify whether the task needs
LdapClient(simplified API, no ODM),LdapTemplate(direct operations with optional ODM), orLdapRepositoryfor query-method derivation. - Configure the
ContextSourcewith URL, base DN, user DN, and password before writing any query or mapping code. - Use ODM annotations (
@Entry,@DnAttribute,@Attribute) when the directory schema maps cleanly to Java types.- Use
DirContextAdapterwhen mapping is dynamic or partial.
- Use
- Build filters with
LdapQueryBuilderorquery().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.
- For authentication checks, search by the directory attribute your deployment uses for login such as
uidorcn, and keep the attribute name explicit in code and tests. - Add an embedded LDAP test that loads a small LDIF file and verifies the query returns the expected entries.
Implementation examples
LdapTemplate search with 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;
});
}
}
LdapTemplate write shape with 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 is too small for a full ODM aggregate workflow.
LdapClient search (Spring LDAP 4.1.0)
@Service
class PersonDirectory {
private final LdapClient ldap;
PersonDirectory(LdapClient ldap) {
this.ldap = ldap;
}
List<Person> findByLastName(String lastName) {
return ldap.search()
.where("objectclass").is("person")
.and("sn").is(lastName)
.list((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;
});
}
}
LdapClient provides list(), stream(), single(), optional(), and map() terminal operations on search queries.
Use LdapClient when ODM is not needed.
LdapClient does not support ODM; use LdapTemplate when ODM entry mapping is required.
ODM entity with DN-relative attributes
@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.
LDAP repository query method
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>*).
Authentication check with explicit login attribute
@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.
Boot-managed repository test shape with embedded LDAP data
@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.
Embedded LDAP test properties shape
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.
Output and configuration shapes
LdapQuery filter shapes
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()
ODM DN-relative mapping shape
@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.
LDAP properties for external server
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.
For embedded LDAPS testing, use spring.ldap.embedded.ssl.bundle instead of configuring spring.ldap.urls manually - see references/embedded-testing-and-ldif.md.
Testing checklist
- Verify queries return the expected entries after loading a known LDIF data set.
- Verify ODM mapping extracts DN components and directory attributes correctly.
- Verify
LdapTemplate.authenticate()returns true for valid credentials and throws for invalid ones. - Verify substring and presence filters match the expected subset of entries.
- For LDAP repositories, verify derived query method names produce the correct filter strings.
- For embedded LDAP tests, verify the test server starts and loads test data before assertions run.
Failure classification
- Treat invalid credentials and bind failures as authentication failures, not retryable infrastructure events.
- Treat malformed filters, schema mismatches, and DN mapping errors as configuration or contract failures.
- Treat timeouts, pool exhaustion, and unavailable servers as infrastructure failures that may justify retry or health-based fail-fast behavior.
Output contract
Return:
- The chosen LDAP access surface and why it fits the task
- The context source and base-DN configuration shape
- The mapping approach, including whether ODM or
DirContextAdapteris used - The query or repository contract the client depends on
- The embedded LDAP test shape and LDIF fixture path
- Any blocker that requires advanced filters, transaction-aware context sources, or deeper ODM mapping
Production checklist
- Externalize directory credentials and never log them.
- Set timeouts on the LDAP context or JNDI environment to avoid thread exhaustion on unresponsive servers.
- Validate LDIF imports against the target directory schema before deployment.
- Monitor directory operation latency and connection pool utilization through application metrics.
- Record the Spring LDAP version in build files or a platform BOM to avoid unexpected compatibility issues.
References
- Open references/advanced-odm-and-repositories.md when the schema needs multi-valued ODM fields, raw
@Query, or deeper repository derivation rules. - Open references/filters-and-dn-handling.md when the task needs complex LDAP filters, escaping, or DN parsing beyond the common path.
- Open references/transactions-and-context-source.md when the application needs transaction-aware context sources or connection-pool tuning.
- Open references/embedded-testing-and-ldif.md when embedded LDAP setup needs custom ports, LDIF handling, LDAPS/SSL, or schema-validation tuning.