spring-data-neo4j — an installable skill for AI agents, published by giuseppe-trisciuoglio/developer-kit.
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)
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 operations
ReactiveNeo4jRepository<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 Neo4jRepository for blocking, imperative operations
Use ReactiveNeo4jRepository for 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 $parameterName for parameter placeholders
Use @Param annotation 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 Neo4jRepository for imperative or ReactiveNeo4jRepository for 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 @DataNeo4jTest for 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 @Transactional is 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
Spring Data Neo4j Official Documentation
Neo4j Developer Guide
Spring Data Commons Documentation
28:[don't have the plugin yet? install it then click "run inline in claude" again.