Comprehensive Java LDAP SDK providing full LDAPv3 protocol support, connection pooling, schema handling, and persistence framework for LDAP directory operations.
—
Object-relational mapping system for treating LDAP entries as Java objects using annotations to define the mapping between Java classes and LDAP directory entries.
Main persister class providing CRUD operations for persistent objects.
/**
* Main persister for object-relational mapping between Java objects and LDAP entries
*/
public class LDAPPersister<T> {
// Constructors
public LDAPPersister(Class<T> type, LDAPInterface ldapInterface) throws LDAPPersistException;
public LDAPPersister(Class<T> type, LDAPInterface ldapInterface, String baseDN) throws LDAPPersistException;
// CRUD operations
public T get(String dn) throws LDAPPersistException;
public T getByRDN(String rdn) throws LDAPPersistException;
public T getByRDN(String parentDN, String rdn) throws LDAPPersistException;
public PersistedObjects<T> search(Filter filter) throws LDAPPersistException;
public PersistedObjects<T> search(String baseDN, SearchScope scope, Filter filter) throws LDAPPersistException;
public PersistedObjects<T> search(String baseDN, SearchScope scope, Filter filter, String... attributes) throws LDAPPersistException;
public PersistedObjects<T> searchForEntries(SearchRequest searchRequest) throws LDAPPersistException;
public void add(T object) throws LDAPPersistException;
public void add(T object, String parentDN) throws LDAPPersistException;
public void modify(T object) throws LDAPPersistException;
public void modify(T object, boolean deleteNullValues) throws LDAPPersistException;
public void modify(T object, boolean deleteNullValues, String... attributesToModify) throws LDAPPersistException;
public void delete(T object) throws LDAPPersistException;
public void delete(String dn) throws LDAPPersistException;
// Configuration
public Class<T> getType();
public LDAPInterface getLDAPInterface();
public String getDefaultParentDN();
public void setDefaultParentDN(String parentDN);
// Utility methods
public Entry encodeEntry(T object, String parentDN) throws LDAPPersistException;
public T decodeEntry(Entry entry) throws LDAPPersistException;
public String constructDN(T object, String parentDN) throws LDAPPersistException;
public Filter createFilter(T object) throws LDAPPersistException;
}Collection wrapper for search results containing persistent objects.
/**
* Collection of persisted objects returned from search operations
*/
public class PersistedObjects<T> implements Iterable<T>, Serializable {
// Iterator access
public Iterator<T> iterator();
public boolean hasMore();
// Size information
public int size();
public boolean isEmpty();
// List conversion
public List<T> toList();
public List<T> toList(int maxObjects);
// Exception handling
public List<LDAPPersistException> getExceptions();
public boolean hasExceptions();
}Class-level annotation defining LDAP object class mapping.
/**
* Class-level annotation for LDAP persistent objects
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LDAPObject {
/**
* Object classes for this type (required)
*/
String[] objectClass();
/**
* Structural object class (optional, defaults to first in objectClass array)
*/
String structuralClass() default "";
/**
* Superior object classes (optional)
*/
String[] superiorClass() default {};
/**
* Default parent DN for new instances (optional)
*/
String defaultParentDN() default "";
/**
* Post-decode method name (optional)
*/
String postDecodeMethod() default "";
/**
* Post-encode method name (optional)
*/
String postEncodeMethod() default "";
}Field-level annotation for attribute mapping.
/**
* Field-level annotation for LDAP attribute mapping
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LDAPField {
/**
* LDAP attribute name (optional, defaults to field name)
*/
String attribute() default "";
/**
* Whether this attribute is part of the RDN (optional)
*/
boolean inRDN() default false;
/**
* Whether this is a required attribute (optional)
*/
boolean requiredForDecode() default false;
/**
* Whether this is a required attribute for encoding (optional)
*/
boolean requiredForEncode() default false;
/**
* Default values for encoding (optional)
*/
String[] defaultEncodeValue() default {};
/**
* Default values for decoding (optional)
*/
String[] defaultDecodeValue() default {};
/**
* Filter usage for this field (optional)
*/
FilterUsage filterUsage() default FilterUsage.CONDITIONALLY_ALLOWED;
/**
* Whether to always include in modification (optional)
*/
boolean alwaysModify() default false;
/**
* Whether to never include in modification (optional)
*/
boolean neverModify() default false;
/**
* Attribute syntax OID (optional)
*/
String syntaxOID() default "";
/**
* Matching rule OID (optional)
*/
String matchingRuleOID() default "";
}
/**
* Filter usage enumeration for field filtering
*/
public enum FilterUsage {
ALWAYS_ALLOWED,
CONDITIONALLY_ALLOWED,
EXCLUDED;
}Special annotation for DN field mapping.
/**
* Field annotation for DN mapping
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LDAPDNField {
// No parameters - indicates this field contains the entry DN
}Annotation for mapping entire entry object.
/**
* Field annotation for whole entry mapping
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LDAPEntryField {
// No parameters - indicates this field contains the Entry object
}Method-level annotations for custom attribute access.
/**
* Method annotation for getter methods
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LDAPGetter {
/**
* LDAP attribute name (required)
*/
String attribute();
/**
* Whether this attribute is part of the RDN (optional)
*/
boolean inRDN() default false;
/**
* Filter usage for this attribute (optional)
*/
FilterUsage filterUsage() default FilterUsage.CONDITIONALLY_ALLOWED;
/**
* Attribute syntax OID (optional)
*/
String syntaxOID() default "";
/**
* Matching rule OID (optional)
*/
String matchingRuleOID() default "";
}
/**
* Method annotation for setter methods
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LDAPSetter {
/**
* LDAP attribute name (required)
*/
String attribute();
/**
* Whether this is a required attribute (optional)
*/
boolean requiredForDecode() default false;
/**
* Whether this is a required attribute for encoding (optional)
*/
boolean requiredForEncode() default false;
/**
* Default values for encoding (optional)
*/
String[] defaultEncodeValue() default {};
/**
* Default values for decoding (optional)
*/
String[] defaultDecodeValue() default {};
/**
* Whether to always include in modification (optional)
*/
boolean alwaysModify() default false;
/**
* Whether to never include in modification (optional)
*/
boolean neverModify() default false;
}Exception class for persistence operations.
/**
* Exception for LDAP persistence operations
*/
public class LDAPPersistException extends Exception {
// Constructors
public LDAPPersistException(String message);
public LDAPPersistException(String message, Throwable cause);
public LDAPPersistException(String message, String matchedDN, ResultCode resultCode, String diagnosticMessage, String[] referralURLs, Control[] controls);
// LDAP result information
public String getMatchedDN();
public ResultCode getResultCode();
public String getDiagnosticMessage();
public String[] getReferralURLs();
public Control[] getControls();
// Object information
public Object getPartiallyDecodedObject();
public void setPartiallyDecodedObject(Object obj);
}Built-in converters for common Java types.
/**
* Interface for custom field value converters
*/
public interface ObjectEncoder {
boolean supportsType(Class<?> type);
String[] encodeFieldValue(Field field, Object value, String objectDN) throws LDAPPersistException;
void decodeField(Field field, Object object, String attribute, String[] values) throws LDAPPersistException;
}
/**
* Default object encoder supporting common Java types
*/
public class DefaultObjectEncoder implements ObjectEncoder {
// Supports: String, primitives, primitive wrappers, arrays, Collections, Date, UUID, etc.
public boolean supportsType(Class<?> type);
public String[] encodeFieldValue(Field field, Object value, String objectDN) throws LDAPPersistException;
public void decodeField(Field field, Object object, String attribute, String[] values) throws LDAPPersistException;
}import com.unboundid.ldap.sdk.*;
import com.unboundid.ldap.sdk.persist.*;
/**
* Simple person object with LDAP mapping
*/
@LDAPObject(objectClass = {"inetOrgPerson"}, defaultParentDN = "ou=people,dc=example,dc=com")
public class Person {
@LDAPDNField
private String dn;
@LDAPField(attribute = "cn", inRDN = true, requiredForEncode = true)
private String commonName;
@LDAPField(attribute = "sn", requiredForEncode = true)
private String surname;
@LDAPField(attribute = "givenName")
private String givenName;
@LDAPField(attribute = "mail")
private String email;
@LDAPField(attribute = "telephoneNumber")
private String[] phoneNumbers;
@LDAPField(attribute = "employeeNumber")
private Integer employeeNumber;
@LDAPField(attribute = "description")
private String description;
// Constructors
public Person() {}
public Person(String commonName, String surname, String email) {
this.commonName = commonName;
this.surname = surname;
this.email = email;
}
// Getters and setters
public String getDn() { return dn; }
public void setDn(String dn) { this.dn = dn; }
public String getCommonName() { return commonName; }
public void setCommonName(String commonName) { this.commonName = commonName; }
public String getSurname() { return surname; }
public void setSurname(String surname) { this.surname = surname; }
public String getGivenName() { return givenName; }
public void setGivenName(String givenName) { this.givenName = givenName; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String[] getPhoneNumbers() { return phoneNumbers; }
public void setPhoneNumbers(String[] phoneNumbers) { this.phoneNumbers = phoneNumbers; }
public Integer getEmployeeNumber() { return employeeNumber; }
public void setEmployeeNumber(Integer employeeNumber) { this.employeeNumber = employeeNumber; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
@Override
public String toString() {
return "Person{dn='" + dn + "', cn='" + commonName + "', sn='" + surname + "'}";
}
}
// Usage
public class PersonManager {
public static void main(String[] args) throws Exception {
LDAPConnection connection = new LDAPConnection("ldap.example.com", 389);
try {
connection.bind("cn=admin,dc=example,dc=com", "password");
// Create persister
LDAPPersister<Person> persister = new LDAPPersister<>(Person.class, connection);
// Create new person
Person john = new Person("John Doe", "Doe", "john.doe@example.com");
john.setGivenName("John");
john.setEmployeeNumber(12345);
john.setPhoneNumbers(new String[]{"+1-555-0123", "+1-555-0124"});
john.setDescription("Software Engineer");
// Add to LDAP
persister.add(john);
System.out.println("Added person: " + john.getDn());
// Retrieve from LDAP
Person retrieved = persister.get(john.getDn());
System.out.println("Retrieved: " + retrieved);
System.out.println("Email: " + retrieved.getEmail());
System.out.println("Employee Number: " + retrieved.getEmployeeNumber());
// Modify person
retrieved.setDescription("Senior Software Engineer");
retrieved.setPhoneNumbers(new String[]{"+1-555-0123", "+1-555-9999"});
persister.modify(retrieved);
System.out.println("Modified person");
// Search for people
Filter searchFilter = Filter.createEqualityFilter("department", "Engineering");
PersistedObjects<Person> results = persister.search(searchFilter);
System.out.println("Search results:");
for (Person person : results) {
System.out.println(" " + person);
}
// Delete person
persister.delete(retrieved);
System.out.println("Deleted person");
} finally {
connection.close();
}
}
}import com.unboundid.ldap.sdk.*;
import com.unboundid.ldap.sdk.persist.*;
import java.util.*;
/**
* Advanced user object with custom attribute handling
*/
@LDAPObject(
objectClass = {"inetOrgPerson", "customUser"},
structuralClass = "inetOrgPerson",
defaultParentDN = "ou=users,dc=example,dc=com"
)
public class User {
@LDAPDNField
private String dn;
@LDAPField(attribute = "cn", inRDN = true)
private String fullName;
@LDAPField(attribute = "sn", requiredForEncode = true)
private String lastName;
@LDAPField(attribute = "givenName")
private String firstName;
@LDAPField(attribute = "mail")
private String primaryEmail;
private Set<String> emailAddresses = new HashSet<>();
private List<String> roles = new ArrayList<>();
private Date lastLoginTime;
private boolean isActive = true;
// Standard getters/setters
public String getDn() { return dn; }
public void setDn(String dn) { this.dn = dn; }
public String getFullName() { return fullName; }
public void setFullName(String fullName) { this.fullName = fullName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getPrimaryEmail() { return primaryEmail; }
public void setPrimaryEmail(String primaryEmail) { this.primaryEmail = primaryEmail; }
// Custom getter/setter methods with LDAP annotations
@LDAPGetter(attribute = "mailAlternateAddress")
public String[] getEmailAddresses() {
return emailAddresses.toArray(new String[0]);
}
@LDAPSetter(attribute = "mailAlternateAddress")
public void setEmailAddresses(String[] addresses) {
emailAddresses.clear();
if (addresses != null) {
Collections.addAll(emailAddresses, addresses);
}
}
public void addEmailAddress(String email) {
emailAddresses.add(email);
}
public void removeEmailAddress(String email) {
emailAddresses.remove(email);
}
@LDAPGetter(attribute = "customRole")
public String[] getRoles() {
return roles.toArray(new String[0]);
}
@LDAPSetter(attribute = "customRole")
public void setRoles(String[] userRoles) {
roles.clear();
if (userRoles != null) {
Collections.addAll(roles, userRoles);
}
}
public void addRole(String role) {
if (!roles.contains(role)) {
roles.add(role);
}
}
public void removeRole(String role) {
roles.remove(role);
}
@LDAPGetter(attribute = "lastLoginTime", syntaxOID = "1.3.6.1.4.1.1466.115.121.1.24")
public String getLastLoginTimeString() {
if (lastLoginTime == null) return null;
return String.valueOf(lastLoginTime.getTime());
}
@LDAPSetter(attribute = "lastLoginTime")
public void setLastLoginTimeString(String timestamp) {
if (timestamp == null || timestamp.isEmpty()) {
lastLoginTime = null;
} else {
try {
lastLoginTime = new Date(Long.parseLong(timestamp));
} catch (NumberFormatException e) {
lastLoginTime = null;
}
}
}
public Date getLastLoginTime() { return lastLoginTime; }
public void setLastLoginTime(Date lastLoginTime) { this.lastLoginTime = lastLoginTime; }
@LDAPGetter(attribute = "customActive")
public String getActiveString() {
return isActive ? "TRUE" : "FALSE";
}
@LDAPSetter(attribute = "customActive", defaultDecodeValue = {"TRUE"})
public void setActiveString(String active) {
isActive = "TRUE".equalsIgnoreCase(active);
}
public boolean isActive() { return isActive; }
public void setActive(boolean active) { isActive = active; }
@Override
public String toString() {
return String.format("User{dn='%s', fullName='%s', primaryEmail='%s', roles=%s, active=%s}",
dn, fullName, primaryEmail, roles, isActive);
}
}import com.unboundid.ldap.sdk.*;
import com.unboundid.ldap.sdk.persist.*;
public class UserSearchExamples {
public static void searchExamples() throws Exception {
LDAPConnection connection = new LDAPConnection("ldap.example.com", 389);
try {
connection.bind("cn=admin,dc=example,dc=com", "password");
LDAPPersister<User> persister = new LDAPPersister<>(User.class, connection);
// Search by email domain
Filter emailDomainFilter = Filter.createSubstringFilter("mail", null, new String[]{"@example.com"}, null);
PersistedObjects<User> domainUsers = persister.search(emailDomainFilter);
System.out.println("Users with @example.com email:");
for (User user : domainUsers) {
System.out.println(" " + user.getFullName() + " - " + user.getPrimaryEmail());
}
// Search by role
Filter roleFilter = Filter.createEqualityFilter("customRole", "admin");
PersistedObjects<User> adminUsers = persister.search(roleFilter);
System.out.println("\nAdmin users:");
for (User user : adminUsers) {
System.out.println(" " + user.getFullName() + " - Roles: " + Arrays.toString(user.getRoles()));
}
// Complex search - active engineering users
Filter complexFilter = Filter.createANDFilter(
Filter.createEqualityFilter("customActive", "TRUE"),
Filter.createORFilter(
Filter.createEqualityFilter("customRole", "engineer"),
Filter.createEqualityFilter("customRole", "senior-engineer"),
Filter.createEqualityFilter("customRole", "architect")
)
);
PersistedObjects<User> engineeringUsers = persister.search(
"ou=users,dc=example,dc=com",
SearchScope.SUB,
complexFilter
);
System.out.println("\nActive engineering users:");
for (User user : engineeringUsers) {
System.out.println(" " + user.getFullName() + " - " + Arrays.toString(user.getRoles()));
}
// Search with limited attributes
PersistedObjects<User> limitedResults = persister.search(
"ou=users,dc=example,dc=com",
SearchScope.ONE,
Filter.createPresenceFilter("mail"),
"cn", "mail", "customRole" // Only retrieve these attributes
);
System.out.println("\nLimited attribute search:");
for (User user : limitedResults) {
System.out.println(" " + user.getFullName() + " - " + user.getPrimaryEmail());
// Note: other fields may be null since they weren't retrieved
}
} finally {
connection.close();
}
}
}import com.unboundid.ldap.sdk.*;
import com.unboundid.ldap.sdk.persist.*;
import java.util.*;
public class BulkOperations {
public static void bulkOperationsExample() throws Exception {
LDAPConnection connection = new LDAPConnection("ldap.example.com", 389);
try {
connection.bind("cn=admin,dc=example,dc=com", "password");
LDAPPersister<User> persister = new LDAPPersister<>(User.class, connection);
// Create multiple users
List<User> newUsers = Arrays.asList(
createUser("Alice Johnson", "Johnson", "Alice", "alice.johnson@example.com", "developer"),
createUser("Bob Smith", "Smith", "Bob", "bob.smith@example.com", "tester"),
createUser("Carol Davis", "Davis", "Carol", "carol.davis@example.com", "analyst"),
createUser("David Wilson", "Wilson", "David", "david.wilson@example.com", "manager")
);
// Add users in batch
List<String> addedDNs = new ArrayList<>();
for (User user : newUsers) {
try {
persister.add(user);
addedDNs.add(user.getDn());
System.out.println("Added: " + user.getFullName());
} catch (LDAPPersistException e) {
System.err.println("Failed to add " + user.getFullName() + ": " + e.getMessage());
}
}
// Bulk modification - add a role to all new users
for (String dn : addedDNs) {
try {
User user = persister.get(dn);
user.addRole("employee");
user.setLastLoginTime(new Date());
// Modify only specific attributes
persister.modify(user, false, "customRole", "lastLoginTime");
System.out.println("Updated roles for: " + user.getFullName());
} catch (LDAPPersistException e) {
System.err.println("Failed to update " + dn + ": " + e.getMessage());
}
}
// Bulk search and update - deactivate users with no recent login
Calendar cutoff = Calendar.getInstance();
cutoff.add(Calendar.MONTH, -6); // 6 months ago
PersistedObjects<User> allUsers = persister.search(Filter.createPresenceFilter("cn"));
for (User user : allUsers) {
if (user.getLastLoginTime() != null && user.getLastLoginTime().before(cutoff.getTime())) {
user.setActive(false);
persister.modify(user, false, "customActive");
System.out.println("Deactivated inactive user: " + user.getFullName());
}
}
// Bulk delete - remove test users
Filter testUserFilter = Filter.createSubstringFilter("cn", "Test", null, null);
PersistedObjects<User> testUsers = persister.search(testUserFilter);
for (User testUser : testUsers) {
try {
persister.delete(testUser);
System.out.println("Deleted test user: " + testUser.getFullName());
} catch (LDAPPersistException e) {
System.err.println("Failed to delete " + testUser.getFullName() + ": " + e.getMessage());
}
}
} finally {
connection.close();
}
}
private static User createUser(String fullName, String lastName, String firstName, String email, String role) {
User user = new User();
user.setFullName(fullName);
user.setLastName(lastName);
user.setFirstName(firstName);
user.setPrimaryEmail(email);
user.addRole(role);
user.setActive(true);
return user;
}
}import com.unboundid.ldap.sdk.*;
import com.unboundid.ldap.sdk.persist.*;
import java.lang.reflect.Field;
import java.util.*;
/**
* Custom encoder for handling special field types
*/
public class CustomObjectEncoder implements ObjectEncoder {
public boolean supportsType(Class<?> type) {
return Map.class.isAssignableFrom(type) ||
Set.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type);
}
public String[] encodeFieldValue(Field field, Object value, String objectDN) throws LDAPPersistException {
if (value == null) {
return new String[0];
}
if (value instanceof Map) {
Map<?, ?> map = (Map<?, ?>) value;
List<String> values = new ArrayList<>();
for (Map.Entry<?, ?> entry : map.entrySet()) {
values.add(entry.getKey() + "=" + entry.getValue());
}
return values.toArray(new String[0]);
}
if (value instanceof Set) {
Set<?> set = (Set<?>) value;
List<String> values = new ArrayList<>();
for (Object item : set) {
values.add(item.toString());
}
return values.toArray(new String[0]);
}
if (value instanceof Date) {
Date date = (Date) value;
return new String[]{String.valueOf(date.getTime())};
}
throw new LDAPPersistException("Unsupported type: " + value.getClass().getSimpleName());
}
public void decodeField(Field field, Object object, String attribute, String[] values) throws LDAPPersistException {
if (values == null || values.length == 0) {
return;
}
try {
field.setAccessible(true);
Class<?> fieldType = field.getType();
if (Map.class.isAssignableFrom(fieldType)) {
Map<String, String> map = new HashMap<>();
for (String value : values) {
String[] parts = value.split("=", 2);
if (parts.length == 2) {
map.put(parts[0], parts[1]);
}
}
field.set(object, map);
}
else if (Set.class.isAssignableFrom(fieldType)) {
Set<String> set = new HashSet<>(Arrays.asList(values));
field.set(object, set);
}
else if (Date.class.isAssignableFrom(fieldType)) {
long timestamp = Long.parseLong(values[0]);
field.set(object, new Date(timestamp));
}
} catch (Exception e) {
throw new LDAPPersistException("Failed to decode field " + field.getName() + ": " + e.getMessage(), e);
}
}
}
/**
* Example object using custom encoding
*/
@LDAPObject(objectClass = {"customObject"})
public class CustomEncodedObject {
@LDAPDNField
private String dn;
@LDAPField(attribute = "cn", inRDN = true)
private String name;
@LDAPField(attribute = "customProperties")
private Map<String, String> properties = new HashMap<>();
@LDAPField(attribute = "customTags")
private Set<String> tags = new HashSet<>();
@LDAPField(attribute = "customTimestamp")
private Date timestamp;
// Getters and setters...
public String getDn() { return dn; }
public void setDn(String dn) { this.dn = dn; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Map<String, String> getProperties() { return properties; }
public void setProperties(Map<String, String> properties) { this.properties = properties; }
public Set<String> getTags() { return tags; }
public void setTags(Set<String> tags) { this.tags = tags; }
public Date getTimestamp() { return timestamp; }
public void setTimestamp(Date timestamp) { this.timestamp = timestamp; }
}Install with Tessl CLI
npx tessl i tessl/maven-com-unboundid--unboundid-ldapsdk