Anıl Şenocak

Observability in Redis OM Spring: Implementing a Command Listener. Redis OM Spring simplifies building applications with Redis by providing powerful abstractions over RedisJSON and RediSearch. It allows developers to map Java objects to Redis documents using familiar Spring Data repositories.
While the abstraction is great for productivity, there are times when you need to see “under the hood.” That's where the CommandListener comes in.
The CommandListener interface allows you to intercept search operations at two critical points: when a search starts and when it finishes.
Here is the implementation of a NoOpCommandListener that logs the exact FT.SEARCH commands executed against Redis:
@Component
@Slf4j
class NoOpCommandListener implements CommandListener {
@Override
public void searchStarted(final String indexName, final Query q, final FTSearchParams params) {
try {
// Accessing private \_queryString field via reflection to see the raw query
final Field field \= q.getClass().getDeclaredField("\_queryString");
field.setAccessible(true);
final String queryString \= (String) field.get(q);
log.info("FT.SEARCH Started - Index: '{}' Query: '{}'", indexName, queryString);
} catch (NoSuchFieldException | IllegalAccessException e) {
log.error("Command started on index: {} (unable to retrieve query string)", indexName);
}
}
@Override
public void searchFinished(final String indexName, final Query q, FTSearchParams params, final SearchResult searchResult) {
try {
final Field field \= q.getClass().getDeclaredField("\_queryString");
field.setAccessible(true);
final String queryString \= (String) field.get(q);
log.info("FT.SEARCH Finished - Index: '{}' Query: '{}' - Total results: {}",
indexName, queryString, searchResult.getTotalResults());
} catch (NoSuchFieldException | IllegalAccessException e) {
log.error("Command finished on index: {} (unable to retrieve query string)", indexName);
}
}
}
To see this in action, consider a simple application managing Person documents.
The Model
We use @Document for RedisJSON mapping and @TextIndexed / @TagIndexed for search indexing.
@Data
@Document
public class Person {
@Id
private Long id;
@TextIndexed
private String firstName;
@TextIndexed
private String lastName;
@TagIndexed
private String email;
}
public interface PersonRepository extends RedisDocumentRepository<Person, Long> {
List<Person> findByLastNameAndFirstName(String lastName, String firstName);
Person findByEmail(String email);
}
When you call an endpoint like /api/persons/name/Doe/John, the NoOpCommandListener will produce logs similar to:
INFO NoOpCommandListener - FT.SEARCH Started - Index: 'com.redis.om.documents.domain.PersonIdx' Query: '@lastName:Doe @firstName:John'
INFO NoOpCommandListener - FT.SEARCH Finished - Index: 'com.redis.om.documents.domain.PersonIdx' Query: '@lastName:Doe @firstName:John' - Total results: 1
Implementing a CommandListener in Redis OM Spring is a powerful way to gain observability into your data layer. Whether you’re debugging complex queries or monitoring search performance, having a window into the commands being sent to Redis is invaluable.
You can find the full demo project here.
Happy coding with Redis!