giuseppe-trisciuoglio/developer-kit3 files

Spring Data Neo4j

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.

Specification
Skill ID
giuseppe-trisciuoglio/developer-kit/spring-data-neo4j
Publisher
giuseppe-trisciuoglio
Repository
developer-kit
Installs
0
Files
3
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

giuseppe-trisciuoglio/developer-kit/spring-data-neo4jInstalls these files
  • SKILL.md
  • references/examples.md
  • references/reference.md

What this skill tells the agent

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

  1. Use `@`Node annotation to mark entity classes
  2. Choose ID strategy:
  • Business key as @Id (immutable, natural identifier)
  • Generated @Id @GeneratedValue (Neo4j internal ID)
  1. Define relationships with @Relationship annotation
  2. Keep entities immutable with final fields
  3. Use `@`Property for custom property names
Validation Checkpoint: If entity save fails, check for constraint violations—duplicate IDs violate uniqueness constraints.

3. Create Repositories

  1. Extend repository interface:
  • Neo4jRepository<Entity, ID> for imperative operations
  • ReactiveNeo4jRepository<Entity, ID> for reactive operations
  1. Use query derivation for simple queries
  2. Apply `@`Query annotation for complex Cypher queries
  3. 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

  1. Use `@`DataNeo4jTest for repository testing with test slicing
  2. Set up Neo4j Harness with embedded database and fixtures
  3. Provide test data via withFixture() Cypher queries
  4. 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{

Use this skill in a project

Open any RiverX project, search the Skills panel for giuseppe-trisciuoglio/developer-kit/spring-data-neo4j, and install it in one click.

Use this skill