spring-data-neo4j
giuseppe-trisciuoglio/developer-kit
Spring Data Neo4j integration patterns for graph database mapping, repositories, and Cypher queries in Spring Boot.
What is spring-data-neo4j?
Provides Spring Data Neo4j integration patterns for Spring Boot applications working with graph databases. Use when you need to map Neo4j nodes and relationships, configure repositories (imperative or reactive), write custom Cypher queries, and test with embedded databases.
- Define node entities with @Node annotation and relationships with @Relationship
- Create imperative and reactive Neo4j repositories with query derivation and @Query custom Cypher queries
- Configure Spring Data Neo4j with connection properties and Cypher-DSL dialect
- Implement graph traversal patterns and relationship navigation
- Set up integration testing with Neo4j embedded databases and test fixtures
- Use business keys or generated IDs for entity identity management
How to install spring-data-neo4j
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill spring-data-neo4j- Spring Boot 2.4+ or 3.0+
- Neo4j 4.0+ database instance or embedded database for testing
- Maven or Gradle build tool
- Java 11+
How to use spring-data-neo4j
- 1.Add spring-boot-starter-data-neo4j dependency via Maven or Gradle
- 2.Configure Neo4j connection URI, username, and password in application.properties
- 3.Define node entities using @Node annotation with @Id and @Relationship annotations
- 4.Create repository interfaces extending Neo4jRepository or ReactiveNeo4jRepository
- 5.Write custom Cypher queries using @Query annotation with $paramName syntax
- 6.Set up integration tests using @DataNeo4jTest with Neo4j Harness and test fixtures
- 7.Verify connection with cypher-shell before deploying to production
Use cases
- Building a movie database application with actors, directors, and relationships
- Querying books by author with custom Cypher queries and filtering
- Testing Neo4j repositories with embedded databases and predefined test data
- Implementing reactive graph queries for non-blocking data access
- Mapping complex graph structures with multiple relationship types and directions
- Spring Boot developers working with Neo4j graph databases
- Backend engineers building relationship-heavy applications
- QA engineers testing Neo4j integrations with embedded databases
- Developers migrating from relational to graph database models
spring-data-neo4j FAQ
Use a business key (@Id without @GeneratedValue) for immutable, natural identifiers. Use @GeneratedValue for Neo4j internal IDs when you need auto-increment behavior, but implement a wither method to maintain immutability.
Use ReactiveNeo4jRepository for non-blocking, reactive operations in high-concurrency scenarios. Use imperative Neo4jRepository for standard blocking operations. Do not mix both in the same application.
Use the @Query annotation with Cypher syntax, parameter placeholders ($paramName), and @Param annotations. Example: @Query("MATCH (b:Book)-[:WRITTEN_BY]->(a:Author) WHERE a.name = $name RETURN b")
Use @DataNeo4jTest with Neo4jBuilders to create an embedded database, load test fixtures via withFixture() Cypher queries, and use @DynamicPropertySource to configure the connection URI.
Check for duplicate IDs violating uniqueness constraints. Ensure your @Id field values are unique across the database, or use @GeneratedValue to let Neo4j manage IDs automatically.
Full instructions (SKILL.md)
Source of truth, from giuseppe-trisciuoglio/developer-kit.
name: spring-data-neo4j description: Provides Spring Data Neo4j integration patterns for Spring Boot applications. Use when you need to work with a graph database, Neo4j nodes and relationships, Cypher queries, or Spring Data Neo4j. Creates node entities with @Node annotation, defines relationships with @Relationship, writes Cypher queries using @Query, configures imperative and reactive Neo4j repositories, implements graph traversal patterns, and sets up testing with embedded databases. allowed-tools: Read, Write, Edit, Bash, Glob, Grep
Spring Data Neo4j Integration Patterns
Overview
Provides Spring Data Neo4j integration patterns for Spring Boot applications. Covers node entity mapping with @Node and @Relationship, repository configuration (imperative and reactive), custom Cypher queries with @Query, and integration testing with embedded Neo4j databases.
When to Use
Use this skill when working with:
- Graph databases and Neo4j integration in Spring Boot
- Node entities, relationships, and Cypher queries
- Spring Data Neo4j repositories (imperative or reactive)
- Neo4j testing with embedded databases
Instructions
1. Set Up Spring Data Neo4j
Add the dependency:
Maven:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
Gradle:
implementation 'org.springframework.boot:spring-boot-starter-data-neo4j'
Configure connection in application.properties:
spring.neo4j.uri=bolt://localhost:7687
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=secret
Configure Cypher-DSL dialect (recommended):
@Configuration
public class Neo4jConfig {
@Bean
Configuration cypherDslConfiguration() {
return Configuration.newConfig()
.withDialect(Dialect.NEO4J_5).build();
}
}
Validation Checkpoint: Run
MATCH (n) RETURN count(n)via cypher-shell to verify the connection works before proceeding.
2. Define Node Entities
- Use
@Node annotation to mark entity classes - Choose ID strategy:
- Business key as
@Id (immutable, natural identifier) - Generated
@Id@GeneratedValue (Neo4j internal ID)
- Business key as
- Define relationships with
@Relationship annotation - Keep entities immutable with final fields
- Use
@Property for custom property names
Validation Checkpoint: If entity save fails, check for constraint violations—duplicate IDs violate uniqueness constraints.
3. Create Repositories
- Extend repository interface:
Neo4jRepository<Entity, ID>for imperative operationsReactiveNeo4jRepository<Entity, ID>for reactive operations
- Use query derivation for simple queries
- Apply
@Query annotation for complex Cypher queries - Use
$paramName syntax for parameters
Validation Checkpoint: Test repository with
findAll()first—if empty, verify the Neo4j instance is running and credentials are correct.
4. Test Your Implementation
- Use
@DataNeo4jTest for repository testing with test slicing - Set up Neo4j Harness with embedded database and fixtures
- Provide test data via
withFixture()Cypher queries - Clean up test data between tests
Validation Checkpoint: If tests fail with "Connection refused", ensure the embedded Neo4j started successfully in
@BeforeAll.
Basic Entity Mapping
Node Entity with Business Key
@Node("Movie")
public class MovieEntity {
@Id
private final String title; // Business key as ID
@Property("tagline")
private final String description;
private final Integer year;
@Relationship(type = "ACTED_IN", direction = Direction.INCOMING)
private List<Roles> actorsAndRoles = new ArrayList<>();
@Relationship(type = "DIRECTED", direction = Direction.INCOMING)
private List<PersonEntity> directors = new ArrayList<>();
public MovieEntity(String title, String description, Integer year) {
this.title = title;
this.description = description;
this.year = year;
}
}
Node Entity with Generated ID
@Node("Movie")
public class MovieEntity {
@Id @GeneratedValue
private Long id;
private final String title;
@Property("tagline")
private final String description;
public MovieEntity(String title, String description) {
this.id = null; // Never set manually
this.title = title;
this.description = description;
}
// Wither method for immutability with generated IDs
public MovieEntity withId(Long id) {
if (this.id != null && this.id.equals(id)) {
return this;
} else {
MovieEntity newObject = new MovieEntity(this.title, this.description);
newObject.id = id;
return newObject;
}
}
}
Repository Patterns
Basic Repository Interface
@Repository
public interface MovieRepository extends Neo4jRepository<MovieEntity, String> {
// Query derivation from method name
MovieEntity findOneByTitle(String title);
List<MovieEntity> findAllByYear(Integer year);
List<MovieEntity> findByYearBetween(Integer startYear, Integer endYear);
}
Reactive Repository
@Repository
public interface MovieRepository extends ReactiveNeo4jRepository<MovieEntity, String> {
Mono<MovieEntity> findOneByTitle(String title);
Flux<MovieEntity> findAllByYear(Integer year);
}
Imperative vs Reactive:
- Use
Neo4jRepositoryfor blocking, imperative operations - Use
ReactiveNeo4jRepositoryfor non-blocking, reactive operations - Do not mix imperative and reactive in the same application
- Reactive requires Neo4j 4+ on the database side
Custom Queries with @Query
@Repository
public interface AuthorRepository extends Neo4jRepository<Author, Long> {
@Query("MATCH (b:Book)-[:WRITTEN_BY]->(a:Author) " +
"WHERE a.name = $name AND b.year > $year " +
"RETURN b")
List<Book> findBooksAfterYear(@Param("name") String name,
@Param("year") Integer year);
@Query("MATCH (b:Book)-[:WRITTEN_BY]->(a:Author) " +
"WHERE a.name = $name " +
"RETURN b ORDER BY b.year DESC")
List<Book> findBooksByAuthorOrderByYearDesc(@Param("name") String name);
}
Custom Query Best Practices:
- Use
$parameterNamefor parameter placeholders - Use
@Paramannotation when parameter name differs from method parameter - MATCH specifies node patterns and relationships
- WHERE filters results
- RETURN defines what to return
Testing Strategies
Neo4j Harness for Integration Testing
Test Configuration:
@DataNeo4jTest
class BookRepositoryIntegrationTest {
private static Neo4j embeddedServer;
@BeforeAll
static void initializeNeo4j() {
embeddedServer = Neo4jBuilders.newInProcessBuilder()
.withDisabledServer() // No HTTP access needed
.withFixture(
"CREATE (b:Book {isbn: '978-0547928210', " +
"name: 'The Fellowship of the Ring', year: 1954})" +
"-[:WRITTEN_BY]->(a:Author {id: 1, name: 'J. R. R. Tolkien'}) " +
"CREATE (b2:Book {isbn: '978-0547928203', " +
"name: 'The Two Towers', year: 1956})" +
"-[:WRITTEN_BY]->(a)"
)
.build();
}
@AfterAll
static void stopNeo4j() {
embeddedServer.close();
}
@DynamicPropertySource
static void neo4jProperties(DynamicPropertyRegistry registry) {
registry.add("spring.neo4j.uri", embeddedServer::boltURI);
registry.add("spring.neo4j.authentication.username", () -> "neo4j");
registry.add("spring.neo4j.authentication.password", () -> "null");
}
@Autowired
private BookRepository bookRepository;
@Test
void givenBookExists_whenFindOneByTitle_thenBookIsReturned() {
Book book = bookRepository.findOneByTitle("The Fellowship of the Ring");
assertThat(book.getIsbn()).isEqualTo("978-0547928210");
}
}
Examples
Example 1: Saving and Retrieving Entities
Input:
MovieEntity movie = new MovieEntity("The Matrix", "Welcome to the Real World", 1999);
movieRepository.save(movie);
MovieEntity found = movieRepository.findOneByTitle("The Matrix");
Output:
MovieEntity{
title="The Matrix",
description="Welcome to the Real World",
year=1999,
actorsAndRoles=[],
directors=[]
}
Example 2: Custom Cypher Query
Input:
List<Book> books = authorRepository.findBooksAfterYear("J.R.R. Tolkien", 1950);
Output:
[
Book{isbn="978-0547928210", name="The Fellowship of the Ring", year=1954},
Book{isbn="978-0547928203", name="The Two Towers", year=1956},
Book{isbn="978-0547928227", name="The Return of the King", year=1957}
]
Example 3: Relationship Traversal
Input:
@Query("MATCH (m:Movie)<-[:ACTED_IN]-(a:Person) " +
"WHERE m.title = $title RETURN a.name as actorName")
List<String> findActorsByMovieTitle(@Param("title") String title);
List<String> actors = movieRepository.findActorsByMovieTitle("The Matrix");
Output:
["Keanu Reeves", "Laurence Fishburne", "Carrie-Anne Moss", "Hugo Weaving"]
Progress from basic to advanced examples covering complete movie database, social network patterns, e-commerce product catalogs, custom queries, and reactive operations.
See examples for comprehensive code examples.
Best Practices
Entity Design
- Use immutable entities with final fields
- Choose between business keys (
@Id) or generated IDs (@Id@GeneratedValue) - Keep entities focused on graph structure, not business logic
- Use proper relationship directions (INCOMING, OUTGOING, UNDIRECTED)
Repository Design
- Extend
Neo4jRepositoryfor imperative orReactiveNeo4jRepositoryfor reactive - Use query derivation for simple queries
- Write custom
@Query for complex graph patterns - Don't mix imperative and reactive in same application
Configuration
- Always configure Cypher-DSL dialect explicitly
- Use environment-specific properties for credentials
- Never hardcode credentials in source code
- Configure connection pooling based on load
Testing
- Use Neo4j Harness for integration tests
- Provide test data via
withFixture()Cypher queries - Use
@DataNeo4jTestfor test slicing - Test both successful and edge-case scenarios
Architecture
- Use constructor injection exclusively
- Separate domain entities from DTOs
- Follow feature-based package structure
- Keep domain layer framework-agnostic
Security
- Use Spring Boot property overrides for credentials
- Configure proper authentication and authorization
- Validate input parameters in service layer
- Use parameterized queries to prevent Cypher injection
Constraints and Warnings
- Do not mix imperative and reactive repositories in the same application.
- Neo4j transactions are required for write operations; ensure
@Transactionalis properly configured. - Be cautious with deep relationship traversal as it can cause performance issues.
- Large result sets should be paginated to avoid memory problems.
- Cypher queries are case-sensitive; ensure consistent casing in property names.
- Immutable entities require proper wither methods for generated IDs.
- Relationships in Spring Data Neo4j are not lazy-loaded by default; consider projection for large graphs.
- The Neo4j Java driver is not compatible with reactive streams; use the reactive driver for reactive operations.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
Connection refused on localhost:7687 | Neo4j server not running | Start Neo4j or use embedded Neo4j for tests |
Authentication failed | Wrong credentials | Check spring.neo4j.authentication.username/password |
Entity not saved / MATCH returns nothing | Transaction not committed | Add @Transactional or verify auto-commit settings |
ConstraintViolationException on save | Duplicate @Id value | Ensure IDs are unique or use @GeneratedValue |
| Relationships missing in results | Wrong @Relationship direction | Check Direction.INCOMING/OUTGOING/UNDIRECTED |
@Query returns wrong data | Cypher parameter syntax | Use $paramName not $ {paramName} |
Test fails with @DataNeo4jTest | Embedded Neo4j not started | Ensure @BeforeAll starts Neo4j before tests |
References
For detailed documentation including complete API reference, Cypher query patterns, and configuration options:
- Annotations Reference
- Cypher Query Language
- Configuration Properties
- Repository Methods
- Projections and DTOs
- Transaction Management
- Performance Tuning
External Resources
Related skills
More from giuseppe-trisciuoglio/developer-kit and the wider catalog.

tailwind-css-patterns
Utility-first Tailwind CSS patterns for responsive, accessible component styling.

tailwind-design-system
Build consistent UI component libraries with Tailwind CSS v4.1+ and shadcn/ui design tokens.

turborepo-monorepo
Comprehensive Turborepo monorepo management for TypeScript/JavaScript projects with workspace setup, task configuration, and CI/CD optimization.

typescript-docs
Generate production-ready TypeScript documentation with JSDoc, TypeDoc, and ADRs for multiple audiences.

typescript-security-review
Security audit for TypeScript/Node.js apps: XSS, injection, CSRF, JWT, CVEs, secrets exposure.

unit-test-application-events
Unit test patterns for Spring ApplicationEvent publishers and @EventListener consumers without booting the full context.