DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Introducing Graph Concepts in Java With Eclipse JNoSQL, Part 3: Understanding Janus
  • Introducing Graph Concepts in Java With Eclipse JNoSQL
  • Simplify NoSQL Database Integration in Java With Eclipse JNoSQL 1.1.3
  • Understanding and Learning NoSQL Databases With Java: Three Key Benefits

Trending

  • Modern Test Automation With AI (LLM) and Playwright MCP
  • The Perfection Trap: Rethinking Parkinson's Law for Modern Engineering Teams
  • Advancing Robot Vision and Control
  • A Guide to Auto-Tagging and Lineage Tracking With OpenMetadata
  1. DZone
  2. Data Engineering
  3. Databases
  4. Introducing Graph Concepts in Java With Eclipse JNoSQL, Part 2: Understanding Neo4j

Introducing Graph Concepts in Java With Eclipse JNoSQL, Part 2: Understanding Neo4j

Learn how to integrate Neo4j into Java apps using Eclipse JNoSQL 1.1.8 — model entities, define relationships, and query graphs with Cypher and Jakarta Data.

By 
Otavio Santana user avatar
Otavio Santana
DZone Core CORE ·
May. 28, 25 · Analysis
Likes (0)
Comment
Save
Tweet
Share
3.4K Views

Join the DZone community and get the full member experience.

Join For Free

Graph databases have rapidly gained popularity in modern software architecture, as systems increasingly rely on relationships, recommendations, and connected data. From social media platforms and fraud detection systems to recommendation engines and knowledge graphs, graph databases offer a powerful way to model and traverse complex relationships that are hard to express efficiently in relational databases.

This second part of the series narrows the focus to Neo4j, the market's most prominent graph database engine. We'll explore its architecture, query language (Cypher), and see how Java developers can leverage Eclipse JNoSQL 1.1.8 to integrate it seamlessly into Java applications.

Understanding Neo4j

Neo4j is a native graph database explicitly built to store and process graphs efficiently and effectively. It represents data as nodes (vertices) and relationships (edges), which can hold properties. Unlike relational databases, where relationships are inferred through foreign keys, Neo4j treats relationships as first-class citizens, resulting in faster and more expressive graph traversals.

Some of the key features that make Neo4j popular include:

  • A powerful query language: Cypher
  • ACID-compliant transactional model
  • High-performance graph traversals
  • Visual data browser and mature tooling
  • Strong community and commercial support

Meet Cypher: The Graph Query Language

Cypher is Neo4j’s declarative query language designed to express graph patterns intuitively. Its syntax is familiar to SQL users but is intended to traverse nodes and relationships, not join tables.

Here’s a quick comparison:

Feature SQL Cypher
Entity Retrieval SELECT * FROM Book MATCH (b:Book) RETURN b
Filtering WHERE name = 'Java' WHERE b.name = 'Java'
Join/Relationship JOIN Book_Category ON... MATCH (b:Book)-[:is]->(c:Category) RETURN b
Grouping & Count GROUP BY category_id WITH c, count(b) AS total
Schema Flexibility Fixed schema Property graph, more dynamic


Getting Started With Neo4j and Eclipse JNoSQL

Eclipse JNoSQL simplifies database integration by adhering to Jakarta EE specifications — specifically, Jakarta NoSQL and Jakarta Data. In this sample, we'll use Java SE and showcase how to interact with Neo4j using a domain model of books and their categories.

First, ensure Neo4j is running. Use Docker to spin it up quickly:

Shell
 
docker run --publish=7474:7474 --publish=7687:7687 --env NEO4J_AUTH=neo4j/admin123 neo4j:5.26.3


Now, configure the connection using MicroProfile Config (which supports environment variable overrides):

Properties files
 
jnosql.neo4j.uri=bolt://localhost:7687
jnosql.neo4j.username=neo4j
jnosql.neo4j.password=admin123
jnosql.graph.database=neo4j


You can overwrite any configuration thanks to the Twelve Application Factor. For example, you can update the production password without changing a single line of code. What you need to do is set the System environment:

Shell
 
export JNOSQL_NEO4J_PASSWORD=PRODUCTION_PASSWORD


Modeling Entities

With Neo4j configured and running, the next step is to define our domain model using Jakarta NoSQL annotations. In this example, we focus on two entities — Book and Category — which will form the core nodes in our graph. These classes will demonstrate how to insert data and define relationships using Neo4j in a clean, idiomatic way with Java.

Java
 
@Entity
public class Book {
    @Id
    private String id;
    @Column
    private String name;
}

@Entity
public class Category {
    @Id
    private String id;
    @Column
    private String name;
}


Eclipse JNoSQL offers a Neo4JTemplate, a specialization of Template, for native Neo4j access. This API allows direct interactions with Neo4j using Cypher queries, edge creation, and entity persistence programmatically and expressively.

Here's how you can encapsulate persistence logic in a basic service layer:

Java
 
@ApplicationScoped
public class BookService {
    private static final Logger LOGGER = Logger.getLogger(BookService.class.getName());

    @Inject
    private Neo4JTemplate template;

    public Book save(Book book) {
        Optional<Book> found = template.select(Book.class).where("name").eq(book.getName()).<Book>singleResult();
        return found.orElseGet(() -> template.insert(book));
    }

    public Category save(Category category) {
        Optional<Category> found = template.select(Category.class).where("name").eq(category.getName()).<Category>singleResult();
        return found.orElseGet(() -> template.insert(category));
    }
}


To demonstrate a full execution cycle, we use the BookApp class, which initializes a CDI container, stores books and categories, creates edges between them, and runs Cypher queries:

Java
 
public final class BookApp {
    private BookApp() {}

    public static void main(String[] args) {
        try (SeContainer container = SeContainerInitializer.newInstance().initialize()) {
            var template = container.select(Neo4JTemplate.class).get();
            var service = container.select(BookService.class).get();

            var software = service.save(Category.of("Software"));
            var java = service.save(Category.of("Java"));
            var architecture = service.save(Category.of("Architecture"));
            var performance = service.save(Category.of("Performance"));

            var effectiveJava = service.save(Book.of("Effective Java"));
            var cleanArchitecture = service.save(Book.of("Clean Architecture"));
            var systemDesign = service.save(Book.of("System Design Interview"));
            var javaPerformance = service.save(Book.of("Java Performance"));

            template.edge(Edge.source(effectiveJava).label("is").target(java).property("relevance", 10).build());
            template.edge(Edge.source(effectiveJava).label("is").target(software).property("relevance", 9).build());
            template.edge(Edge.source(cleanArchitecture).label("is").target(software).property("relevance", 8).build());
            template.edge(Edge.source(cleanArchitecture).label("is").target(architecture).property("relevance", 10).build());
            template.edge(Edge.source(systemDesign).label("is").target(architecture).property("relevance", 9).build());
            template.edge(Edge.source(systemDesign).label("is").target(software).property("relevance", 7).build());
            template.edge(Edge.source(javaPerformance).label("is").target(performance).property("relevance", 8).build());
            template.edge(Edge.source(javaPerformance).label("is").target(java).property("relevance", 9).build());

            System.out.println("Books in 'Architecture' category:");
            var architectureBooks = template.cypher("MATCH (b:Book)-[:is]->(c:Category {name: 'Architecture'}) RETURN b AS book", Collections.emptyMap()).toList();
            architectureBooks.forEach(doc -> System.out.println(" - " + doc));

            System.out.println("Categories with more than one book:");
            var commonCategories = template.cypher("MATCH (b:Book)-[:is]->(c:Category) WITH c, count(b) AS total WHERE total > 1 RETURN c", Collections.emptyMap()).toList();
            commonCategories.forEach(doc -> System.out.println(" - " + doc));

            var highRelevanceBooks = template.cypher("MATCH (b:Book)-[r:is]->(:Category) WHERE r.relevance >= 9 RETURN DISTINCT b", Collections.emptyMap()).toList();
            System.out.println(" Books with high relevance:");
            highRelevanceBooks.forEach(doc -> System.out.println(" - " + doc));

            System.out.println(" Books with name: 'Effective Java':");
            var effectiveJavaBooks = template.cypher("MATCH (b:Book {name: $name}) RETURN b", Collections.singletonMap("name", "Effective Java")).toList();
            effectiveJavaBooks.forEach(doc -> System.out.println(" - " + doc));
        }
    }
}


You can also develop relationships and execute Cypher queries. The example below shows how to define an edge between two entities using the explicitly Edge API provided by Eclipse JNoSQL. This edge represents a relationship with the label is and includes a property, relevance to express its importance.

Java
 
Edge<Book, Category> edge = Edge.source(book).label("is").target(category).property("relevance", 9).build();
template.edge(edge);


After creating edges, you can use Cypher to query the graph. For instance, the following query retrieves books that have a high relevance relationship (>= 9) to any category:

Java
 
var books = template.cypher(
    "MATCH (b:Book)-[r:is]->(:Category) WHERE r.relevance >= 9 RETURN DISTINCT b",
    Collections.emptyMap()
).toList();


These examples demonstrate how Neo4JTemplate can persist and relate domain entities and navigate and analyze graph structures with Cypher.

Refer to BookApp in the sample for data setup, insertion, relationship creation, and Cypher queries. After inserting that information into the database, you can check the Ne4J dashboard:

Ne4J dashboard

You can also interact with Neo4j using repository interfaces. Eclipse JNoSQL supports Neo4JRepository a Jakarta Data extension with Cypher support:

Java
 

@Repository
public interface BookRepository extends Neo4JRepository<Book, String> {
    Optional<Book> findByName(String name);

    @Cypher("MATCH (b:Book)-[:is]->(c:Category {name: 'Architecture'}) RETURN DISTINCT b")
    List<Book> findArchitectureBooks();

    @Cypher("MATCH (b:Book)-[r:is]->(:Category) WHERE r.relevance >= 9 RETURN DISTINCT b")
    List<Book> highRelevanceBooks();
}

@Repository
public interface CategoryRepository extends Neo4JRepository<Category, String> {
    Optional<Category> findByName(String name);

    @Cypher("MATCH (b:Book)-[:is]->(c:Category) WITH c, count(b) AS total WHERE total > 1 RETURN c")
    List<Category> commonCategories();
}


The BookApp2 class demonstrates how to use these repositories in practice by replacing the low-level Neo4JTemplate usage with Jakarta Data's repository abstraction. This approach simplifies the code significantly while allowing access to Cypher's expressive power through annotations.

This example not only shows how to persist entities using standard repository methods like findByName, but also how to perform complex graph queries through the @Cypher annotation. Additionally, the edge creation is still handled via GraphTemplate, keeping the relationship modeling fully explicit and under control.

This dual-model — repositories for domain access and templates for graph-specific relationships — offers a great balance between convenience and flexibility, making it ideal for complex domain models with rich relationships.

Java
 
import jakarta.enterprise.inject.se.SeContainer;
import jakarta.enterprise.inject.se.SeContainerInitializer;
import org.eclipse.jnosql.mapping.graph.Edge;
import org.eclipse.jnosql.mapping.graph.GraphTemplate;

public final class BookApp2 {

    private BookApp2() {
    }

    public static void main(String[] args) {

        try (SeContainer container = SeContainerInitializer.newInstance().initialize()) {
            var template = container.select(GraphTemplate.class).get();
            var bookRepository = container.select(BookRepository.class).get();
            var repository = container.select(CategoryRepository.class).get();

            var software = repository.findByName("Software").orElseGet(() -> repository.save(Category.of("Software")));
            var java = repository.findByName("Java").orElseGet(() -> repository.save(Category.of("Java")));
            var architecture = repository.findByName("Architecture").orElseGet(() -> repository.save(Category.of("Architecture")));
            var performance = repository.findByName("Performance").orElseGet(() -> repository.save(Category.of("Performance")));

            var effectiveJava = bookRepository.findByName("Effective Java").orElseGet(() -> bookRepository.save(Book.of("Effective Java")));
            var cleanArchitecture = bookRepository.findByName("Clean Architecture").orElseGet(() -> bookRepository.save(Book.of("Clean Architecture")));
            var systemDesign = bookRepository.findByName("System Design Interview").orElseGet(() -> bookRepository.save(Book.of("System Design Interview")));
            var javaPerformance = bookRepository.findByName("Java Performance").orElseGet(() -> bookRepository.save(Book.of("Java Performance")));

            template.edge(Edge.source(effectiveJava).label("is").target(java).property("relevance", 10).build());
            template.edge(Edge.source(effectiveJava).label("is").target(software).property("relevance", 9).build());
            template.edge(Edge.source(cleanArchitecture).label("is").target(software).property("relevance", 8).build());
            template.edge(Edge.source(cleanArchitecture).label("is").target(architecture).property("relevance", 10).build());
            template.edge(Edge.source(systemDesign).label("is").target(architecture).property("relevance", 9).build());
            template.edge(Edge.source(systemDesign).label("is").target(software).property("relevance", 7).build());
            template.edge(Edge.source(javaPerformance).label("is").target(performance).property("relevance", 8).build());
            template.edge(Edge.source(javaPerformance).label("is").target(java).property("relevance", 9).build());

            System.out.println("Books in 'Architecture' category:");
            var architectureBooks = bookRepository.findArchitectureBooks();
            architectureBooks.forEach(doc -> System.out.println(" - " + doc));

            System.out.println("Categories with more than one book:");
            var commonCategories = repository.commonCategories();
            commonCategories.forEach(doc -> System.out.println(" - " + doc));

            var highRelevanceBooks = bookRepository.highRelevanceBooks();

            System.out.println("Books with high relevance:");
            highRelevanceBooks.forEach(doc -> System.out.println(" - " + doc));

            var bookByName = bookRepository.queryByName("Effective Java");
            System.out.println("Book by name: " + bookByName);
        }
    }
}


Conclusion

Neo4j offers powerful graph capabilities that Java developers can now access in a clean, standard way using Eclipse JNoSQL and Jakarta Data. Whether you choose to interact via Neo4JTemplate or leverage Jakarta Data repositories. The integration is smooth, type-safe, and expressive. This approach lets you model complex relationships natively without sacrificing Java idioms or developer productivity.

Resources

  • Eclipse JNoSQL Project
  • Jakarta Data Specification
  • Sample Project
Eclipse Neo4j Graph (Unix) Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Introducing Graph Concepts in Java With Eclipse JNoSQL, Part 3: Understanding Janus
  • Introducing Graph Concepts in Java With Eclipse JNoSQL
  • Simplify NoSQL Database Integration in Java With Eclipse JNoSQL 1.1.3
  • Understanding and Learning NoSQL Databases With Java: Three Key Benefits

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends: