Codion and Complexity

A Comparison Experiment

A note on method before the numbers: this is a single experiment, conducted by the Codion author, implementing the same small application in several stacks. The non-Codion line counts are estimates for idiomatic implementations, not competition-grade golf, and reasonable people will arrive at somewhat different numbers. The Codion implementation is reproduced in full below, so at least one side of the comparison you can judge for yourself.

CRUD Implementation Comparison

The baseline was a simple customer management application with:

Codion Implementation

The complete Codion implementation required just over 140 lines of code:

package is.codion.demos.store;

import is.codion.common.db.database.Database;
import is.codion.common.user.User;
import is.codion.framework.db.EntityConnectionProvider;
import is.codion.framework.db.local.LocalEntityConnectionProvider;
import is.codion.framework.domain.DomainModel;
import is.codion.framework.domain.DomainType;
import is.codion.framework.domain.entity.EntityType;
import is.codion.framework.domain.entity.attribute.Column;
import is.codion.framework.domain.entity.attribute.Column.Generator;
import is.codion.framework.domain.entity.attribute.ForeignKey;
import is.codion.swing.framework.model.SwingEntityApplicationModel;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.model.SwingEntityModel;
import is.codion.swing.framework.ui.EntityApplication;
import is.codion.swing.framework.ui.EntityApplicationPanel;
import is.codion.swing.framework.ui.EntityEditPanel;
import is.codion.swing.framework.ui.EntityPanel;

import java.util.List;

import static is.codion.framework.domain.DomainType.domainType;

public final class StoreAppPanel extends EntityApplicationPanel<SwingEntityApplicationModel> {

  public static final DomainType DOMAIN = domainType("Store");

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

    Column<Long> ID = TYPE.longColumn("id");
    Column<String> FIRST_NAME = TYPE.stringColumn("first_name");
  }

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

    Column<Long> ID = TYPE.longColumn("id");
    Column<String> STREET = TYPE.stringColumn("street");
    Column<Long> CUSTOMER_ID = TYPE.longColumn("customer_id");
    ForeignKey CUSTOMER_FK = TYPE.foreignKey("customer_fk", CUSTOMER_ID, Customer.ID);
  }

  private static class Store extends DomainModel {

    private Store() {
      super(DOMAIN);
      add(Customer.TYPE.as()
              .attributes(
                      Customer.ID.as()
                              .primaryKey()
                              .generator(Generator.identity()),
                      Customer.FIRST_NAME.as()
                              .column()
                              .caption("First name")
                              .nullable(false)
                              .maximumLength(40))
              .caption("Customer")
              .build());
      
      add(Address.TYPE.as()
              .attributes(
                      Address.ID.as()
                              .primaryKey()
                              .generator(Generator.identity()),
                      Address.STREET.as()
                              .column()
                              .nullable(false)
                              .maximumLength(120)
                              .caption("Street"),
                      Address.CUSTOMER_ID.as()
                              .column(),
                      Address.CUSTOMER_FK.as()
                              .foreignKey()
                              .caption("Customer"))
              .caption("Address")
              .build());
    }
  }

  public StoreAppPanel(SwingEntityApplicationModel applicationModel) {
    super(applicationModel, createEntityPanels(applicationModel), List.of());
  }

  private static List<EntityPanel> createEntityPanels(SwingEntityApplicationModel applicationModel) {
    SwingEntityModel customerModel = applicationModel.models().get(Customer.TYPE);
    SwingEntityModel addressModel = customerModel.detail().get(Address.TYPE);

    EntityPanel customerPanel = new EntityPanel(customerModel, new CustomerEditPanel(customerModel.editModel()));
    EntityPanel addressPanel = new EntityPanel(addressModel, new AddressEditPanel(addressModel.editModel()));
    customerPanel.detail().add(addressPanel);

    return List.of(customerPanel);
  }

  private static SwingEntityApplicationModel createApplicationModel(EntityConnectionProvider connectionProvider) {
    SwingEntityModel customerModel = new SwingEntityModel(Customer.TYPE, connectionProvider);
    SwingEntityModel addressModel = new SwingEntityModel(Address.TYPE, connectionProvider);
    customerModel.detail().add(addressModel);

    return new SwingEntityApplicationModel(connectionProvider, List.of(customerModel));
  }

  private static class CustomerEditPanel extends EntityEditPanel {

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

    @Override
    protected void initializeUI() {
      create().textField(Customer.FIRST_NAME);
      addInputPanel(Customer.FIRST_NAME);
    }
  }

  private static class AddressEditPanel extends EntityEditPanel {

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

    @Override
    protected void initializeUI() {
      create().comboBox(Address.CUSTOMER_FK);
      create().textField(Address.STREET);
      addInputPanel(Address.CUSTOMER_FK);
      addInputPanel(Address.STREET);
    }
  }

  public static void main(String[] args) {
    Database.URL.set("jdbc:h2:mem:h2db");
    Database.INIT_SCRIPTS.set("src/main/sql/create_schema.sql");
    EntityApplication.builder(SwingEntityApplicationModel.class, StoreAppPanel.class)
            .connectionProvider(LocalEntityConnectionProvider.builder()
                    .domain(new Store())
                    .user(User.parse("scott:tiger"))
                    .build())
            .model(StoreAppPanel::createApplicationModel)
            .start();
  }
}

This implementation includes:

Framework Comparison Results

Framework Lines of Code Cost of adding master-detail
Codion 143 +51 lines (+55%)
Spring Boot + Thymeleaf ~355 +170 lines (+92%)
JavaFX + Spring Boot ~500 +210 lines (+72%)
Vaadin + Spring Boot ~335 +155 lines (+86%)
React + Node.js ~420 +180 lines (+75%)
Angular + .NET Core ~480 +200 lines (+71%)

Key Observations

The Cost of a Detail Table

Adding a simple master-detail relationship revealed clear differences:

Manual vs. Automatic Patterns

Traditional frameworks required manual implementation of:

Codion handles these patterns automatically through its framework design.

Feature Parity Gap

Despite having 2-3x more code, other frameworks still lacked many of Codion’s built-in features:

Feature Parity Requirements

Here’s what’s needed to achieve feature parity with Codion using other frameworks:

Basic Business Table Requirements

Feature Lines of Code Framework Implementation
Sortable columns ~50 Custom sort logic, state management, UI indicators
Resizable columns ~100 Mouse event handlers, column width calculations, persistence
Column reordering ~150 Drag & drop implementation, array manipulation, state sync
Per-column filtering ~200 Filter UI components, query building, debounced search
Keyboard navigation ~300 Key event handlers, focus management, selection logic
Export functionality ~150 Data serialization, clipboard API, format options
Multi-row selection ~100 Selection state, Ctrl/Shift logic, visual indicators
Modified field indicators ~100 Dirty checking, visual states, form coordination
Master-detail coordination ~500 Manual filtering, refresh orchestration, state sync
Professional validation ~200 Field-level validation, error display, form states

Total additional lines needed: ~1,850

This transforms our comparison from:

To the reality of feature parity:

Development Time Impact

Rough estimates of what the code difference means in practice:

Development time:

Maintenance:

What usually happens instead: Applications ship without the features:

Conclusion

The pattern this experiment suggests: many frameworks leave developers to hand-implement functionality that could be framework-provided. For this class of application the difference was roughly an order of magnitude in code once feature parity was accounted for - along with the development time and maintenance surface that code implies, or the quiet feature cuts made to avoid writing it.

Codion takes a different approach - optimizing specifically for business application requirements. Its development has focused on business application needs. Explore the complete Codion philosophy →

These findings suggest that some internal business applications could benefit from desktop implementations using domain-focused frameworks. This presents an alternative to web deployment for certain enterprise tools. Read why desktop applications still matter →