Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
.claude/skills/giuseppe-trisciuoglio-spring-data-neo4j/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 141% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 245% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 216% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 138% | 0% |
| case-13 | ✓→✗ | ▼ Worse | 94% | 0% |
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.
Use this skill when working with:
Add the dependency:
Maven:
xml<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-neo4j</artifactId> </dependency>
Gradle:
groovyimplementation 'org.springframework.boot:spring-boot-starter-data-neo4j'
Configure connection in application.properties:
propertiesspring.neo4j.uri=bolt://localhost:7687 spring.neo4j.authentication.username=neo4j spring.neo4j.authentication.password=secret
Configure Cypher-DSL dialect (recommended):
java@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.
@Node annotation to mark entity classes@Id (immutable, natural identifier)@Id @GeneratedValue (Neo4j internal ID)@Relationship annotation@Property for custom property names> Validation Checkpoint: If entity save fails, check for constraint violations—duplicate IDs violate uniqueness constraints.
Neo4jRepository<Entity, ID> for imperative operationsReactiveNeo4jRepository<Entity, ID> for reactive operations@Query annotation for complex Cypher queries$paramName syntax for parameters> Validation Checkpoint: Test repository with findAll() first—if empty, verify the Neo4j instance is running and credentials are correct.
@DataNeo4jTest for repository testing with test slicingwithFixture() Cypher queries> Validation Checkpoint: If tests fail with "Connection refused", ensure the embedded Neo4j started successfully in @BeforeAll.
java@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; } }
java@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; } } }
java@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); }
java@Repository public interface MovieRepository extends ReactiveNeo4jRepository<MovieEntity, String> { Mono<MovieEntity> findOneByTitle(String title); Flux<MovieEntity> findAllByYear(Integer year); }
Imperative vs Reactive:
Neo4jRepository for blocking, imperative operationsReactiveNeo4jRepository for non-blocking, reactive operations@Queryjava@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:
$parameterName for parameter placeholders@Param annotation when parameter name differs from method parameterTest Configuration:
java@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"); } }
Input:
javaMovieEntity movie = new MovieEntity("The Matrix", "Welcome to the Real World", 1999); movieRepository.save(movie); MovieEntity found = movieRepository.findOneByTitle("The Matrix");
Output:
javaMovieEntity{ title="The Matrix", description="Welcome to the Real World", year=1999, actorsAndRoles=[], directors=[] }
Input:
javaList<Book> books = authorRepository.findBooksAfterYear("J.R.R. Tolkien", 1950);
Output:
java[ 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} ]
Input:
java@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:
java["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.
@Id) or generated IDs (@Id @GeneratedValue)Neo4jRepository for imperative or ReactiveNeo4jRepository for reactive@Query for complex graph patternswithFixture() Cypher queries@DataNeo4jTest for test slicing@Transactional is properly configured.| 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 |
For detailed documentation including complete API reference, Cypher query patterns, and configuration options:
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 11,651 | 10,872 | -7% | 1 | 1 | 0% | 2,510 | 6,045 | +141% | 0 | 0 | — |
case-02 | pass→pass | 10,667 | 4,738 | -56% | 1 | 1 | 0% | 2,168 | 4,183 | +93% | 0 | 0 | — |
case-03 | pass→pass | 8,912 | 8,333 | -6% | 1 | 1 | 0% | 1,520 | 4,278 | +181% | 0 | 0 | — |
case-04 | pass→pass | 13,824 | 10,018 | -28% | 1 | 1 | 0% | 2,724 | 5,356 | +97% | 0 | 0 | — |
case-05 | pass→pass | 20,295 | 12,167 | -40% | 1 | 1 | 0% | 2,962 | 5,509 | +86% | 0 | 0 | — |
case-06 | pass→pass | 15,201 | 8,486 | -44% | 1 | 1 | 0% | 2,499 | 4,830 | +93% | 0 | 0 | — |
case-07 | pass→pass | 3,588 | 3,324 | -7% | 1 | 1 | 0% | 497 | 3,761 | +657% | 0 | 0 | — |
case-08 | pass→pass | 6,934 | 5,804 | -16% | 1 | 1 | 0% | 1,266 | 4,393 | +247% | 0 | 0 | — |
case-09 | pass→pass | 5,536 | 3,037 | -45% | 1 | 1 | 0% | 1,002 | 3,753 | +275% | 0 | 0 | — |
case-10 | pass→pass | 3,944 | 2,582 | -35% | 1 | 1 | 0% | 775 | 3,751 | +384% | 0 | 0 | — |
case-11 | fail→fail | 7,744 | 5,419 | -30% | 1 | 1 | 0% | 1,446 | 4,459 | +208% | 0 | 0 | — |
case-12 | fail→pass | 6,574 | 8,373 | +27% | 1 | 1 | 0% | 1,290 | 4,455 | +245% | 0 | 0 | — |
case-13 | pass→fail | 15,391 | 11,022 | -28% | 1 | 1 | 0% | 2,811 | 5,467 | +94% | 0 | 0 | — |
case-14 | fail→pass | 7,033 | 3,890 | -45% | 1 | 1 | 0% | 1,285 | 4,065 | +216% | 0 | 0 | — |
case-15 | pass→pass | 5,557 | 5,760 | +4% | 1 | 1 | 0% | 956 | 4,343 | +354% | 0 | 0 | — |
case-16 | fail→pass | 9,968 | 4,412 | -56% | 1 | 1 | 0% | 1,724 | 4,107 | +138% | 0 | 0 | — |
case-17 | pass→pass | 9,415 | 7,296 | -23% | 1 | 1 | 0% | 1,600 | 4,519 | +182% | 0 | 0 | — |
case-18 | pass→pass | 12,232 | 5,613 | -54% | 1 | 1 | 0% | 2,211 | 4,432 | +100% | 0 | 0 | — |
case-19 | pass→pass | 10,986 | 5,598 | -49% | 1 | 1 | 0% | 1,601 | 4,283 | +168% | 0 | 0 | — |
case-20 | pass→pass | 9,083 | 8,409 | -7% | 1 | 1 | 0% | 1,641 | 4,863 | +196% | 0 | 0 | — |
case-21 | pass→pass | 11,527 | 9,516 | -17% | 1 | 1 | 0% | 2,110 | 5,262 | +149% | 0 | 0 | — |
case-22 | pass→pass | 9,593 | 6,868 | -28% | 1 | 1 | 0% | 1,745 | 4,650 | +166% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +14 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.