Building an ER Diagram Generator in Java: From Concepts to Implementation

Entity-Relationship (ER) Diagrams are essential tools for database design, providing a visual representation of entities, their attributes, and relationships. Creating an ER Diagram Generator in Java involves parsing database metadata or user input and transforming it into visual diagrams. This article explores various approaches, from simple text-based generators to sophisticated visual tools.


Approach Overview

There are several ways to build an ER Diagram Generator in Java:

  1. Text-to-Diagram: Parse text descriptions and generate diagrams
  2. Database Reverse Engineering: Connect to databases and generate diagrams from schema
  3. Visual Designer: Create a GUI application for designing diagrams interactively
  4. Code Generation: Generate SQL DDL from visual diagrams

Option 1: Simple Text-Based ER Diagram Generator

Let's start with a basic generator that parses a custom text format and creates a simple visual representation.

1.1 Define the Domain Model:

import java.util.*;
// Entity class
class Entity {
private String name;
private List<Attribute> attributes;
private boolean isWeakEntity;
public Entity(String name) {
this.name = name;
this.attributes = new ArrayList<>();
this.isWeakEntity = false;
}
// Getters and setters
public String getName() { return name; }
public List<Attribute> getAttributes() { return attributes; }
public boolean isWeakEntity() { return isWeakEntity; }
public void setWeakEntity(boolean weakEntity) { isWeakEntity = weakEntity; }
public void addAttribute(Attribute attribute) {
this.attributes.add(attribute);
}
}
// Attribute class
class Attribute {
private String name;
private String type;
private boolean isPrimaryKey;
private boolean isForeignKey;
public Attribute(String name, String type) {
this.name = name;
this.type = type;
this.isPrimaryKey = false;
this.isForeignKey = false;
}
// Getters and setters
public String getName() { return name; }
public String getType() { return type; }
public boolean isPrimaryKey() { return isPrimaryKey; }
public void setPrimaryKey(boolean primaryKey) { isPrimaryKey = primaryKey; }
public boolean isForeignKey() { return isForeignKey; }
public void setForeignKey(boolean foreignKey) { isForeignKey = foreignKey; }
}
// Relationship class
class Relationship {
private String name;
private Entity entity1;
private Entity entity2;
private String cardinality; // "one-to-one", "one-to-many", "many-to-many"
public Relationship(String name, Entity entity1, Entity entity2, String cardinality) {
this.name = name;
this.entity1 = entity1;
this.entity2 = entity2;
this.cardinality = cardinality;
}
// Getters
public String getName() { return name; }
public Entity getEntity1() { return entity1; }
public Entity getEntity2() { return entity2; }
public String getCardinality() { return cardinality; }
}

1.2 Text Parser:

class ERTextParser {
public static ERSchema parseText(String text) {
ERSchema schema = new ERSchema();
String[] lines = text.split("\n");
Entity currentEntity = null;
for (String line : lines) {
line = line.trim();
if (line.startsWith("ENTITY:")) {
String entityName = line.substring(7).trim();
currentEntity = new Entity(entityName);
schema.addEntity(currentEntity);
} 
else if (line.startsWith("ATTRIBUTE:") && currentEntity != null) {
String[] parts = line.substring(10).trim().split(":");
if (parts.length >= 2) {
Attribute attr = new Attribute(parts[0].trim(), parts[1].trim());
if (parts.length > 2) {
if (parts[2].contains("PK")) attr.setPrimaryKey(true);
if (parts[2].contains("FK")) attr.setForeignKey(true);
}
currentEntity.addAttribute(attr);
}
}
else if (line.startsWith("RELATIONSHIP:")) {
String[] parts = line.substring(13).trim().split("->");
if (parts.length == 3) {
Entity e1 = schema.getEntity(parts[0].trim());
Entity e2 = schema.getEntity(parts[2].trim());
if (e1 != null && e2 != null) {
Relationship rel = new Relationship(
"Rel_" + e1.getName() + "_" + e2.getName(),
e1, e2, parts[1].trim()
);
schema.addRelationship(rel);
}
}
}
else if (line.startsWith("WEAK_ENTITY:") && currentEntity != null) {
currentEntity.setWeakEntity(true);
}
}
return schema;
}
}

1.3 Simple ASCII Diagram Generator:

class ASCIIDiagramGenerator {
public static String generateASCIIDiagram(ERSchema schema) {
StringBuilder diagram = new StringBuilder();
diagram.append("ER DIAGRAM\n");
diagram.append("==========\n\n");
// Generate entities
for (Entity entity : schema.getEntities()) {
diagram.append(generateEntityBox(entity)).append("\n\n");
}
// Generate relationships
diagram.append("RELATIONSHIPS:\n");
for (Relationship rel : schema.getRelationships()) {
diagram.append(generateRelationshipLine(rel)).append("\n");
}
return diagram.toString();
}
private static String generateEntityBox(Entity entity) {
StringBuilder box = new StringBuilder();
String header = entity.isWeakEntity() ? 
"[" + entity.getName() + "] (Weak)" : 
"[" + entity.getName() + "]";
box.append("┌").append("─".repeat(header.length())).append("┐\n");
box.append("│").append(header).append("│\n");
box.append("├").append("─".repeat(header.length())).append("┤\n");
for (Attribute attr : entity.getAttributes()) {
String attrLine = attr.getName() + " : " + attr.getType();
if (attr.isPrimaryKey()) attrLine = "PK: " + attrLine;
if (attr.isForeignKey()) attrLine = "FK: " + attrLine;
box.append("│").append(attrLine);
// Add padding
int padding = header.length() - attrLine.length();
if (padding > 0) {
box.append(" ".repeat(padding));
}
box.append("│\n");
}
box.append("└").append("─".repeat(header.length())).append("┘");
return box.toString();
}
private static String generateRelationshipLine(Relationship rel) {
return String.format("%s --(%s)--> %s",
rel.getEntity1().getName(),
rel.getCardinality(),
rel.getEntity2().getName());
}
}

1.4 Schema Container:

class ERSchema {
private List<Entity> entities;
private List<Relationship> relationships;
public ERSchema() {
this.entities = new ArrayList<>();
this.relationships = new ArrayList<>();
}
public void addEntity(Entity entity) {
this.entities.add(entity);
}
public void addRelationship(Relationship relationship) {
this.relationships.add(relationship);
}
public Entity getEntity(String name) {
return entities.stream()
.filter(e -> e.getName().equals(name))
.findFirst()
.orElse(null);
}
public List<Entity> getEntities() { return entities; }
public List<Relationship> getRelationships() { return relationships; }
}

1.5 Usage Example:

public class TextBasedERGenerator {
public static void main(String[] args) {
String erText = """
ENTITY: Student
ATTRIBUTE: student_id:int:PK
ATTRIBUTE: name:varchar
ATTRIBUTE: email:varchar
ENTITY: Course
ATTRIBUTE: course_id:int:PK
ATTRIBUTE: title:varchar
ATTRIBUTE: credits:int
RELATIONSHIP: Student -> many-to-many -> Course
""";
ERSchema schema = ERTextParser.parseText(erText);
String diagram = ASCIIDiagramGenerator.generateASCIIDiagram(schema);
System.out.println(diagram);
}
}

Option 2: Database Reverse Engineering ER Generator

This approach connects to a database and generates ER diagrams from existing schema.

2.1 Database Metadata Reader:

import java.sql.*;
import java.util.*;
class DatabaseSchemaReader {
private Connection connection;
public DatabaseSchemaReader(String url, String username, String password) 
throws SQLException {
this.connection = DriverManager.getConnection(url, username, password);
}
public ERSchema readSchema(String schemaName) throws SQLException {
ERSchema schema = new ERSchema();
Map<String, Entity> entities = new HashMap<>();
// Read tables (entities)
DatabaseMetaData metaData = connection.getMetaData();
ResultSet tables = metaData.getTables(null, schemaName, null, new String[]{"TABLE"});
while (tables.next()) {
String tableName = tables.getString("TABLE_NAME");
Entity entity = new Entity(tableName);
entities.put(tableName, entity);
// Read columns (attributes)
readColumns(metaData, tableName, entity);
schema.addEntity(entity);
}
// Read foreign keys (relationships)
readRelationships(metaData, entities, schema);
return schema;
}
private void readColumns(DatabaseMetaData metaData, String tableName, Entity entity) 
throws SQLException {
ResultSet columns = metaData.getColumns(null, null, tableName, null);
ResultSet primaryKeys = metaData.getPrimaryKeys(null, null, tableName);
Set<String> pkColumns = new HashSet<>();
while (primaryKeys.next()) {
pkColumns.add(primaryKeys.getString("COLUMN_NAME"));
}
while (columns.next()) {
String columnName = columns.getString("COLUMN_NAME");
String columnType = columns.getString("TYPE_NAME");
Attribute attribute = new Attribute(columnName, columnType);
if (pkColumns.contains(columnName)) {
attribute.setPrimaryKey(true);
}
entity.addAttribute(attribute);
}
}
private void readRelationships(DatabaseMetaData metaData, 
Map<String, Entity> entities, 
ERSchema schema) throws SQLException {
for (String tableName : entities.keySet()) {
ResultSet foreignKeys = metaData.getImportedKeys(null, null, tableName);
while (foreignKeys.next()) {
String fkTableName = foreignKeys.getString("FKTABLE_NAME");
String pkTableName = foreignKeys.getString("PKTABLE_NAME");
String fkColumnName = foreignKeys.getString("FKCOLUMN_NAME");
Entity fkEntity = entities.get(fkTableName);
Entity pkEntity = entities.get(pkTableName);
if (fkEntity != null && pkEntity != null) {
// Mark attribute as foreign key
fkEntity.getAttributes().stream()
.filter(attr -> attr.getName().equals(fkColumnName))
.findFirst()
.ifPresent(attr -> attr.setForeignKey(true));
// Create relationship
Relationship relationship = new Relationship(
"FK_" + fkTableName + "_" + pkTableName,
fkEntity, pkEntity, "many-to-one"
);
schema.addRelationship(relationship);
}
}
}
}
public void close() throws SQLException {
if (connection != null) {
connection.close();
}
}
}

Option 3: Visual ER Diagram Generator with JavaFX

For a graphical ER diagram, you can use JavaFX or Swing. Here's a basic structure:

3.1 JavaFX Entity Node:

import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
class EntityNode extends VBox {
private Entity entity;
private Rectangle border;
public EntityNode(Entity entity, double x, double y) {
this.entity = entity;
this.setLayoutX(x);
this.setLayoutY(y);
// Create visual representation
createVisual();
}
private void createVisual() {
// Header
Text header = new Text(entity.getName());
header.setStyle("-fx-font-weight: bold; -fx-font-size: 14;");
// Attributes
VBox attributesBox = new VBox(5);
for (Attribute attr : entity.getAttributes()) {
String attributeText = attr.getName() + " : " + attr.getType();
Text attrText = new Text(attributeText);
if (attr.isPrimaryKey()) {
attrText.setStyle("-fx-font-weight: bold; -fx-fill: blue;");
} else if (attr.isForeignKey()) {
attrText.setStyle("-fx-font-style: italic; -fx-fill: green;");
}
attributesBox.getChildren().add(attrText);
}
// Border
border = new Rectangle();
border.setStroke(Color.BLACK);
border.setFill(Color.TRANSPARENT);
border.setStrokeWidth(2);
if (entity.isWeakEntity()) {
border.setStrokeDashArray(javafx.collections.FXCollections.observableArrayList(5.0, 5.0));
}
this.getChildren().addAll(header, attributesBox);
// Bind border to content size
border.widthProperty().bind(this.widthProperty().add(10));
border.heightProperty().bind(this.heightProperty().add(10));
border.setLayoutX(-5);
border.setLayoutY(-5);
this.getChildren().add(0, border);
}
public Entity getEntity() { return entity; }
}

3.2 Main JavaFX Application:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class VisualERGenerator extends Application {
private Pane canvas;
private ERSchema schema;
@Override
public void start(Stage primaryStage) {
canvas = new Pane();
Scene scene = new Scene(canvas, 1200, 800);
// Load schema (from database or text)
loadSampleSchema();
generateVisualDiagram();
primaryStage.setTitle("ER Diagram Generator");
primaryStage.setScene(scene);
primaryStage.show();
}
private void loadSampleSchema() {
// Create sample schema or load from database
schema = new ERSchema();
Entity student = new Entity("Student");
student.addAttribute(new Attribute("id", "INT"));
student.addAttribute(new Attribute("name", "VARCHAR"));
schema.addEntity(student);
Entity course = new Entity("Course");
course.addAttribute(new Attribute("course_id", "INT"));
course.addAttribute(new Attribute("title", "VARCHAR"));
schema.addEntity(course);
Relationship enrollment = new Relationship(
"Enrollment", student, course, "many-to-many"
);
schema.addRelationship(enrollment);
}
private void generateVisualDiagram() {
double x = 50;
double y = 50;
// Create entity nodes
for (Entity entity : schema.getEntities()) {
EntityNode entityNode = new EntityNode(entity, x, y);
canvas.getChildren().add(entityNode);
x += 300; // Position next entity
if (x > 800) {
x = 50;
y += 200;
}
}
// TODO: Create relationship lines
// This would involve calculating positions and drawing lines
}
public static void main(String[] args) {
launch(args);
}
}

Option 4: Exporting to Standard Formats

4.1 Graphviz DOT Format Export:

class GraphvizExporter {
public static String exportToDOT(ERSchema schema) {
StringBuilder dot = new StringBuilder();
dot.append("digraph ERDiagram {\n");
dot.append("  rankdir=TB;\n");
dot.append("  node [shape=record, fontname=\"Arial\"];\n\n");
// Define entities
for (Entity entity : schema.getEntities()) {
dot.append("  ").append(entity.getName().replace(" ", "_"))
.append(" [label=\"{").append(entity.getName());
if (entity.isWeakEntity()) {
dot.append(" (Weak)");
}
dot.append("|");
// Add attributes
for (int i = 0; i < entity.getAttributes().size(); i++) {
Attribute attr = entity.getAttributes().get(i);
if (attr.isPrimaryKey()) {
dot.append(" <").append(attr.getName()).append("> ");
dot.append("PK: ").append(attr.getName());
} else {
dot.append(attr.getName());
}
dot.append(" : ").append(attr.getType());
if (i < entity.getAttributes().size() - 1) {
dot.append("\\n");
}
}
dot.append("}\"];\n");
}
dot.append("\n");
// Define relationships
for (Relationship rel : schema.getRelationships()) {
dot.append("  ").append(rel.getEntity1().getName().replace(" ", "_"))
.append(" -> ")
.append(rel.getEntity2().getName().replace(" ", "_"))
.append(" [label=\"").append(rel.getCardinality()).append("\"];\n");
}
dot.append("}\n");
return dot.toString();
}
}

Integration with Existing Libraries

For production use, consider integrating with existing libraries:

  • Graphviz Java: Use with the DOT export to generate PNG/SVG images
  • JGraphX (mxGraph): Powerful diagramming library
  • PlantUML: Integrate with PlantUML for various diagram formats
  • Apache Batik: For SVG generation and manipulation

Example with PlantUML Integration:

class PlantUMLExporter {
public static String exportToPlantUML(ERSchema schema) {
StringBuilder plantuml = new StringBuilder();
plantuml.append("@startuml\n");
plantuml.append("!define table(x) entity x << (T,#FFAAAA) >>\n");
plantuml.append("hide circle\n\n");
// Define entities
for (Entity entity : schema.getEntities()) {
plantuml.append("entity ").append(entity.getName()).append(" {\n");
for (Attribute attr : entity.getAttributes()) {
String prefix = "";
if (attr.isPrimaryKey()) prefix += "*";
if (attr.isForeignKey()) prefix += "+";
plantuml.append("  ").append(prefix).append(attr.getName())
.append(" : ").append(attr.getType()).append("\n");
}
plantuml.append("}\n\n");
}
// Define relationships
for (Relationship rel : schema.getRelationships()) {
plantuml.append(rel.getEntity1().getName()).append(" ||--o{ ")
.append(rel.getEntity2().getName()).append(" : \"")
.append(rel.getName()).append("\"\n");
}
plantuml.append("@enduml");
return plantuml.toString();
}
}

Best Practices for ER Diagram Generator

  1. Modular Design: Separate parsing, modeling, and rendering concerns
  2. Support Multiple Output Formats: ASCII, PNG, SVG, PDF
  3. Interactive Features: Drag-and-drop, zoom, pan for visual generators
  4. Validation: Check for circular references, orphan entities
  5. Performance: Handle large schemas efficiently with lazy loading
  6. Export Options: SQL DDL, JSON, XML formats

Conclusion

Building an ER Diagram Generator in Java involves:

  1. Modeling: Creating domain classes for entities, attributes, and relationships
  2. Input Processing: Parsing text, database metadata, or user input
  3. Rendering: Generating visual representations (ASCII, GUI, or exported formats)
  4. Integration: Working with existing diagramming libraries and tools

The complexity can range from simple text-based generators to sophisticated visual tools with database connectivity. By following a modular approach, you can create a flexible ER diagram generator that meets specific requirements while maintaining extensibility for future enhancements.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper