1. Rich client

This artifact pulls in all required framework dependencies, except the ones related to database connectivity and logging, see below.

Client Artifact

Swing

is.codion:codion-swing-framework.ui:0.18.25

2. Database connectivity

A Codion client has three ways of connecting to a database, directly via a local JDBC connection or remotely via RMI or HTTP using the Codion remote server.

DB Connection Artifact

Local

is.codion:codion-framework-db-local:0.18.25

RMI

is.codion:codion-framework-db-rmi:0.18.25

HTTP

is.codion:codion-framework-db-http:0.18.25

2.1. DBMS

When connecting to the database with a local JDBC connection the DBMS module for the underlying database must be on the classpath. Note that these artifacts do not depend on the JDBC drivers, so those must be added separately.

The most used and tested DBMS modules are:

  1. Oracle

  2. PostgreSQL

  3. H2 Database

DBMS Artifact

Db2

is.codion:codion-dbms-db2:0.18.25

Derby

is.codion:codion-dbms-derby:0.18.25

H2

is.codion:codion-dbms-h2:0.18.25

HSQL

is.codion:codion-dbms-hsql:0.18.25

MariaDB

is.codion:codion-dbms-mariadb:0.18.25

MySQL

is.codion:codion-dbms-mysql:0.18.25

Oracle

is.codion:codion-dbms-oracle:0.18.25

PostgreSQL

is.codion:codion-dbms-postgresql:0.18.25

SQLite

is.codion:codion-dbms-sqlite:0.18.25

SQL Server

is.codion:codion-dbms-sqlserver:0.18.25

3. Logging

Codion uses SLF4J throughout so all you need to do is add a SLF4J bridge for your logging framework of choice to the classpath. If you use Logback, Log4J or Java Util Logging you can use one of the logging-proxy plugins below which will pull in the required dependencies and provide a main-menu action in the client for setting the logging level.

Logging Artifact

Logback

is.codion:codion-plugin-logback-proxy:0.18.25

Log4j

is.codion:codion-plugin-log4j-proxy:0.18.25

Java Util Logging

is.codion:codion-plugin-jul-proxy:0.18.25

4. Gradle

A minimal Gradle dependencies configuration for a Swing based client application, using the H2 database.

dependencies {
    implementation "is.codion:codion-swing-framework-ui:0.18.25"

    runtimeOnly "is.codion:codion-framework-db-local:0.18.25"
    runtimeOnly "is.codion:codion-dbms-h2:0.18.25"
    runtimeOnly "com.h2database:h2:2.3.230"
    runtimeOnly "is.codion:codion-plugin-logback-proxy:0.18.25"

    testImplementation "is.codion:codion-framework-domain-test:0.18.25"
    testImplementation "org.junit.jupiter:junit-jupiter-api:5.10.3"
    testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.10.3"
}

5. Code examples

There are at minimum three steps involved in creating a Codion application:

  • Defining a domain model based on the underlying tables.

  • Creating edit panels for tables requiring CRUD functionality.

  • Assembling a client application from these building blocks.

5.1. Domain

Module Artifact

is.codion.framework.domain

is.codion:codion-framework-domain:0.18.25

Note
The domain module is transitively included via the UI module.

5.1.1. Defining entities

The below examples are somewhat simplified, but functionally correct.

View example schema SQL
Note
Not all columns are used in the example code below.
create user if not exists scott password 'tiger';
alter user scott admin true;

create schema store;

create table store.customer (
  id varchar(36) not null,
  first_name varchar(40) not null,
  last_name varchar(40) not null,
  email varchar(100),
  active boolean default true not null,
  constraint customer_pk primary key (id),
  constraint customer_email_uk unique (email)
);

create table store.address (
  id identity not null,
  street varchar(120) not null,
  city varchar(50) not null,
  valid boolean default true not null,
  constraint address_pk primary key (id),
  constraint address_uk unique (street, city)
);

create table store.customer_address (
  id identity not null,
  customer_id varchar(36) not null,
  address_id integer not null,
  constraint customer_address_pk primary key (id),
  constraint customer_address_uk unique (customer_id, address_id)
);

insert into store.customer(id, first_name, last_name, email)
values ('ff60ebc3-bae0-4c0f-b094-4129edd3665a', 'John', 'Doe', 'doe@doe.net');

insert into store.address(street, city)
values ('Elm Street 123', 'Syracuse');

insert into store.customer_address(customer_id, address_id)
values ('ff60ebc3-bae0-4c0f-b094-4129edd3665a', 1);

commit;
View full domain source
public class Store extends DomainModel {

  public static final DomainType DOMAIN = domainType(Store.class);

  public Store() {
    super(DOMAIN);
    add(customer(), address(), customerAddress());
  }

  public interface Customer {
    EntityType TYPE = DOMAIN.entityType("store.customer");

    Column<Integer> ID = TYPE.integerColumn("id");
    Column<String> FIRST_NAME = TYPE.stringColumn("first_name");
    Column<String> LAST_NAME = TYPE.stringColumn("last_name");
  }

  EntityDefinition customer() {
    return Customer.TYPE.define(
                    Customer.ID.define()
                            .primaryKey(),
                    Customer.FIRST_NAME.define()
                            .column()
                            .caption("First name")
                            .nullable(false)
                            .maximumLength(40),
                    Customer.LAST_NAME.define()
                            .column()
                            .caption("Last name")
                            .nullable(false)
                            .maximumLength(40))
            .keyGenerator(identity())
            .stringFactory(StringFactory.builder()
                    .value(Customer.LAST_NAME)
                    .text(", ")
                    .value(Customer.FIRST_NAME)
                    .build())
            .build();
  }

  public interface Address {
    EntityType TYPE = DOMAIN.entityType("store.address");

    Column<Integer> ID = TYPE.integerColumn("id");
    Column<String> STREET = TYPE.stringColumn("street");
    Column<String> CITY = TYPE.stringColumn("city");
  }

  EntityDefinition address() {
    return Address.TYPE.define(
                    Address.ID.define()
                            .primaryKey(),
                    Address.STREET.define()
                            .column()
                            .caption("Street")
                            .nullable(false)
                            .maximumLength(120),
                    Address.CITY.define()
                            .column()
                            .caption("City")
                            .nullable(false)
                            .maximumLength(50))
            .keyGenerator(automatic("store.address"))
            .stringFactory(StringFactory.builder()
                    .value(Address.STREET)
                    .text(", ")
                    .value(Address.CITY)
                    .build())
            .build();
  }

  public interface CustomerAddress {
    EntityType TYPE = DOMAIN.entityType("store.customer_address");

    Column<Integer> ID = TYPE.integerColumn("id");
    Column<Integer> CUSTOMER_ID = TYPE.integerColumn("customer_id");
    Column<Integer> ADDRESS_ID = TYPE.integerColumn("address_id");

    ForeignKey CUSTOMER_FK = TYPE.foreignKey("customer_fk", CUSTOMER_ID, Customer.ID);
    ForeignKey ADDRESS_FK = TYPE.foreignKey("address_fk", ADDRESS_ID, Address.ID);
  }

  EntityDefinition customerAddress() {
    return CustomerAddress.TYPE.define(
                    CustomerAddress.ID.define()
                            .primaryKey(),
                    CustomerAddress.CUSTOMER_ID.define()
                            .column()
                            .nullable(false),
                    CustomerAddress.CUSTOMER_FK.define()
                            .foreignKey()
                            .caption("Customer"),
                    CustomerAddress.ADDRESS_ID.define()
                            .column()
                            .nullable(false),
                    CustomerAddress.ADDRESS_FK.define()
                            .foreignKey()
                            .caption("Address"))
            .keyGenerator(automatic("store.customer_address"))
            .caption("Customer address")
            .build();
  }
}

To create a domain model we extend the DomainModel class and define a DomainType constant to identify the domain model.

In the constructor we add three entity definitions to the domain model.

This class is continued below.

public class Store extends DomainModel {

  public static final DomainType DOMAIN = domainType(Store.class);

  public Store() {
    super(DOMAIN);
    add(customer(), address(), customerAddress());
  }

We create a namespace interface and define a EntityType constant based on the STORE.CUSTOMER table and typed Column constants for each column, these constants specify the entity API, used when referring to the entity type or its columns.

public interface Customer {
  EntityType TYPE = DOMAIN.entityType("store.customer");

  Column<Integer> ID = TYPE.integerColumn("id");
  Column<String> FIRST_NAME = TYPE.stringColumn("first_name");
  Column<String> LAST_NAME = TYPE.stringColumn("last_name");
}

In the associated method we create ColumnDefinition.Builder instances based on each Column, providing further configuration options, such as specifying a caption and maximum length.

We supply these ColumnDefinition.Builder instances to the EntityType.define() method, which returns a EntityDefinition.Builder instance, providing further configuration options for the entity definition, such as the key generation strategy. Finally we build the EntityDefinition and return it.

EntityDefinition customer() {
  return Customer.TYPE.define(
                  Customer.ID.define()
                          .primaryKey(),
                  Customer.FIRST_NAME.define()
                          .column()
                          .caption("First name")
                          .nullable(false)
                          .maximumLength(40),
                  Customer.LAST_NAME.define()
                          .column()
                          .caption("Last name")
                          .nullable(false)
                          .maximumLength(40))
          .keyGenerator(identity())
          .stringFactory(StringFactory.builder()
                  .value(Customer.LAST_NAME)
                  .text(", ")
                  .value(Customer.FIRST_NAME)
                  .build())
          .build();
}

Next we define an entity based on the table STORE.ADDRESS.

public interface Address {
  EntityType TYPE = DOMAIN.entityType("store.address");

  Column<Integer> ID = TYPE.integerColumn("id");
  Column<String> STREET = TYPE.stringColumn("street");
  Column<String> CITY = TYPE.stringColumn("city");
}

EntityDefinition address() {
  return Address.TYPE.define(
                  Address.ID.define()
                          .primaryKey(),
                  Address.STREET.define()
                          .column()
                          .caption("Street")
                          .nullable(false)
                          .maximumLength(120),
                  Address.CITY.define()
                          .column()
                          .caption("City")
                          .nullable(false)
                          .maximumLength(50))
          .keyGenerator(automatic("store.address"))
          .stringFactory(StringFactory.builder()
                  .value(Address.STREET)
                  .text(", ")
                  .value(Address.CITY)
                  .build())
          .build();
}

And finally we define an entity based on the many-to-many relationship table STORE.CUSTOMER_ADDRESS, note the foreign keys.

public interface CustomerAddress {
  EntityType TYPE = DOMAIN.entityType("store.customer_address");

  Column<Integer> ID = TYPE.integerColumn("id");
  Column<Integer> CUSTOMER_ID = TYPE.integerColumn("customer_id");
  Column<Integer> ADDRESS_ID = TYPE.integerColumn("address_id");

  ForeignKey CUSTOMER_FK = TYPE.foreignKey("customer_fk", CUSTOMER_ID, Customer.ID);
  ForeignKey ADDRESS_FK = TYPE.foreignKey("address_fk", ADDRESS_ID, Address.ID);
}

EntityDefinition customerAddress() {
  return CustomerAddress.TYPE.define(
                  CustomerAddress.ID.define()
                          .primaryKey(),
                  CustomerAddress.CUSTOMER_ID.define()
                          .column()
                          .nullable(false),
                  CustomerAddress.CUSTOMER_FK.define()
                          .foreignKey()
                          .caption("Customer"),
                  CustomerAddress.ADDRESS_ID.define()
                          .column()
                          .nullable(false),
                  CustomerAddress.ADDRESS_FK.define()
                          .foreignKey()
                          .caption("Address"))
          .keyGenerator(automatic("store.customer_address"))
          .caption("Customer address")
          .build();
}

5.2. UI

Module Artifact

is.codion.swing.framework.ui

is.codion:codion-swing-framework-ui:0.18.25

The EntityPanel class provides a Swing UI for viewing and editing entities, it is composed of a EntityEditPanel for editing and a EntityTablePanel which provides a table view.

Each of these panel classes has a corresponding model class.

UI class Model class

EntityPanel

SwingEntityModel

EntityEditPanel

SwingEntityEditModel

EntityTablePanel

SwingEntityTableModel

The EntityEditPanel class is the only abstract one, you’ll need to extend it and implement the initializeUI() method, which initializes the input form.

Below we demonstrate how to set up a simple master/detail panel.

5.2.1. Master

We extend EntityEditPanel to provide the UI for editing a customer.

public class CustomerEditPanel extends EntityEditPanel {

  public CustomerEditPanel(SwingEntityEditModel editModel) {
    super(editModel);
  }

  @Override
  protected void initializeUI() {
    focus().initial().set(Customer.FIRST_NAME);
    createTextField(Customer.FIRST_NAME);
    createTextField(Customer.LAST_NAME);
    addInputPanel(Customer.FIRST_NAME);
    addInputPanel(Customer.LAST_NAME);
  }
}

We build a EntityConnectionProvider used when we create a SwingEntityModel based on the Customer entity.

We create an instance of our CustomerEditPanel and use it along with the model to create a EntityPanel.

EntityConnectionProvider connectionProvider =
        LocalEntityConnectionProvider.builder()
                .domain(new Store())
                .user(User.parse("scott:tiger"))
                .build();

SwingEntityModel customerModel =
        new SwingEntityModel(Customer.TYPE, connectionProvider);

CustomerEditPanel customerEditPanel =
        new CustomerEditPanel(customerModel.editModel());

EntityPanel customerPanel =
        new EntityPanel(customerModel, customerEditPanel);

5.2.2. Detail

Here we create a panel for viewing and editing customer addresses, much like the one above.

public class AddressEditPanel extends EntityEditPanel {

  public AddressEditPanel(SwingEntityEditModel editModel) {
    super(editModel);
  }

  @Override
  protected void initializeUI() {
    focus().initial().set(CustomerAddress.CUSTOMER_FK);
    createComboBox(CustomerAddress.CUSTOMER_FK);
    createComboBox(CustomerAddress.ADDRESS_FK);
    addInputPanel(CustomerAddress.CUSTOMER_FK);
    addInputPanel(CustomerAddress.ADDRESS_FK);
  }
}

We create a SwingEntityModel based on the CustomerAddress entity, adding it as a detail model on the customer model from above.

Finally, we create a EntityPanel for the customer address and add that as a detail panel on the customer panel.

SwingEntityModel addressModel =
        new SwingEntityModel(CustomerAddress.TYPE, connectionProvider);

customerModel.detailModels().add(addressModel);

AddressEditPanel addressEditPanel =
        new AddressEditPanel(addressModel.editModel());

EntityPanel addressPanel =
        new EntityPanel(addressModel, addressEditPanel);

customerPanel.detailPanels().add(addressPanel);

//lazy initialization of UI components
customerPanel.initialize();

//populate the table model with data from the database
customerModel.tableModel().items().refresh();

Dialogs.componentDialog(customerPanel)
        .title("Customers")
        .show();

5.3. Domain unit test

The DomainTest class provides a way to unit test the domain model. The test method performs insert, update, select and delete on a randomly generated entity instance and verifies the results.

Module Artifact

is.codion.framework.domain.test

is.codion:codion-framework-domain-test:0.18.25

public final class StoreTest extends DomainTest {

  public StoreTest() {
    super(new Store(), User.parse("scott:tiger"));
  }

  @Test
  void customer() {
    test(Customer.TYPE);
  }

  @Test
  void address() {
    test(Address.TYPE);
  }

  @Test
  void customerAddress() {
    test(CustomerAddress.TYPE);
  }
}

5.4. Persistance

The EntityConnection interface provides select, insert, update, and delete methods. It has three available implementations, one of which is based on a local JDBC connection, used below.

Module Artifact Description

is.codion.framework.db.core

is.codion:codion-framework-db-core:0.18.25

Core database API

is.codion.framework.db.local

is.codion:codion-framework-db-local:0.18.25

Local JDBC implementation

5.4.1. Selecting

Store domain = new Store();

EntityConnection connection =
        LocalEntityConnection.localEntityConnection(
                Database.instance(), domain, User.parse("scott:tiger"));

//select customer
Entity johnDoe = // where last name = Doe
        connection.selectSingle(Customer.LAST_NAME.equalTo("Doe"));

//select all customer addresses
List<Entity> customerAddresses = //where customer = john doe
        connection.select(CustomerAddress.CUSTOMER_FK.equalTo(johnDoe));

Entity customerAddress = customerAddresses.get(0);

Entity address = customerAddress.entity(CustomerAddress.ADDRESS_FK);

String lastName = johnDoe.get(Customer.LAST_NAME);
String street = address.get(Address.STREET);
String city = address.get(Address.CITY);

5.4.2. Persisting

Store domain = new Store();

EntityConnection connection =
        LocalEntityConnection.localEntityConnection(
                Database.instance(), domain, User.parse("scott:tiger"));

Entities entities = domain.entities();

Entity customer = entities.builder(Customer.TYPE)
        .with(Customer.FIRST_NAME, "John")
        .with(Customer.LAST_NAME, "Doe")
        .build();

customer = connection.insertSelect(customer);

Entity address = entities.builder(Address.TYPE)
        .with(Address.STREET, "Elm Street 321")
        .with(Address.CITY, "Syracuse")
        .build();

address = connection.insertSelect(address);

Entity customerAddress = entities.builder(CustomerAddress.TYPE)
        .with(CustomerAddress.CUSTOMER_FK, customer)
        .with(CustomerAddress.ADDRESS_FK, address)
        .build();

customerAddress = connection.insertSelect(customerAddress);

customer.put(Customer.FIRST_NAME, "Jonathan");

connection.update(customer);

connection.delete(customerAddress.primaryKey());
Full code
/*
 * This file is part of Codion.
 *
 * Codion is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Codion is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Codion.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Copyright (c) 2004 - 2025, Björn Darri Sigurðsson.
 */
package is.codion.manual.quickstart;

import is.codion.common.db.database.Database;
import is.codion.common.user.User;
import is.codion.framework.db.EntityConnection;
import is.codion.framework.db.EntityConnectionProvider;
import is.codion.framework.db.local.LocalEntityConnection;
import is.codion.framework.db.local.LocalEntityConnectionProvider;
import is.codion.framework.domain.entity.Entities;
import is.codion.framework.domain.entity.Entity;
import is.codion.swing.common.ui.dialog.Dialogs;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.model.SwingEntityModel;
import is.codion.swing.framework.ui.EntityEditPanel;
import is.codion.swing.framework.ui.EntityPanel;

import java.util.List;

import static is.codion.manual.quickstart.Store.*;

public final class Example {

  public class CustomerEditPanel extends EntityEditPanel {

    public CustomerEditPanel(SwingEntityEditModel editModel) {
      super(editModel);
    }

    @Override
    protected void initializeUI() {
      focus().initial().set(Customer.FIRST_NAME);
      createTextField(Customer.FIRST_NAME);
      createTextField(Customer.LAST_NAME);
      addInputPanel(Customer.FIRST_NAME);
      addInputPanel(Customer.LAST_NAME);
    }
  }

  public class AddressEditPanel extends EntityEditPanel {

    public AddressEditPanel(SwingEntityEditModel editModel) {
      super(editModel);
    }

    @Override
    protected void initializeUI() {
      focus().initial().set(CustomerAddress.CUSTOMER_FK);
      createComboBox(CustomerAddress.CUSTOMER_FK);
      createComboBox(CustomerAddress.ADDRESS_FK);
      addInputPanel(CustomerAddress.CUSTOMER_FK);
      addInputPanel(CustomerAddress.ADDRESS_FK);
    }
  }

  void customerPanel() {
    EntityConnectionProvider connectionProvider =
            LocalEntityConnectionProvider.builder()
                    .domain(new Store())
                    .user(User.parse("scott:tiger"))
                    .build();

    SwingEntityModel customerModel =
            new SwingEntityModel(Customer.TYPE, connectionProvider);

    CustomerEditPanel customerEditPanel =
            new CustomerEditPanel(customerModel.editModel());

    EntityPanel customerPanel =
            new EntityPanel(customerModel, customerEditPanel);

    SwingEntityModel addressModel =
            new SwingEntityModel(CustomerAddress.TYPE, connectionProvider);

    customerModel.detailModels().add(addressModel);

    AddressEditPanel addressEditPanel =
            new AddressEditPanel(addressModel.editModel());

    EntityPanel addressPanel =
            new EntityPanel(addressModel, addressEditPanel);

    customerPanel.detailPanels().add(addressPanel);

    //lazy initialization of UI components
    customerPanel.initialize();

    //populate the table model with data from the database
    customerModel.tableModel().items().refresh();

    Dialogs.componentDialog(customerPanel)
            .title("Customers")
            .show();
  }

  void selectEntities() {
    Store domain = new Store();

    EntityConnection connection =
            LocalEntityConnection.localEntityConnection(
                    Database.instance(), domain, User.parse("scott:tiger"));

    //select customer
    Entity johnDoe = // where last name = Doe
            connection.selectSingle(Customer.LAST_NAME.equalTo("Doe"));

    //select all customer addresses
    List<Entity> customerAddresses = //where customer = john doe
            connection.select(CustomerAddress.CUSTOMER_FK.equalTo(johnDoe));

    Entity customerAddress = customerAddresses.get(0);

    Entity address = customerAddress.entity(CustomerAddress.ADDRESS_FK);

    String lastName = johnDoe.get(Customer.LAST_NAME);
    String street = address.get(Address.STREET);
    String city = address.get(Address.CITY);
  }

  void persistEntities() {
    Store domain = new Store();

    EntityConnection connection =
            LocalEntityConnection.localEntityConnection(
                    Database.instance(), domain, User.parse("scott:tiger"));

    Entities entities = domain.entities();

    Entity customer = entities.builder(Customer.TYPE)
            .with(Customer.FIRST_NAME, "John")
            .with(Customer.LAST_NAME, "Doe")
            .build();

    customer = connection.insertSelect(customer);

    Entity address = entities.builder(Address.TYPE)
            .with(Address.STREET, "Elm Street 321")
            .with(Address.CITY, "Syracuse")
            .build();

    address = connection.insertSelect(address);

    Entity customerAddress = entities.builder(CustomerAddress.TYPE)
            .with(CustomerAddress.CUSTOMER_FK, customer)
            .with(CustomerAddress.ADDRESS_FK, address)
            .build();

    customerAddress = connection.insertSelect(customerAddress);

    customer.put(Customer.FIRST_NAME, "Jonathan");

    connection.update(customer);

    connection.delete(customerAddress.primaryKey());
  }
}