Skip to content

CrudEditorForm and CrudCreationForm

When using Crud directly, the Editor and CreationForm type parameters must implement these two interfaces. They are the contract between the form object and the framework’s save/create actions.


The edit form interface. Mateu calls save() when the user submits the edit form, and uses id() to navigate back to the detail view after saving.

public interface CrudEditorForm<IdType> {
void save(HttpRequest httpRequest);
IdType id();
}
MethodCalled whenMust do
save(httpRequest)User clicks Save in the edit formPersist the current field values
id()After save, to redirect to the detail viewReturn the entity’s identifier
public class ProductEditor implements CrudEditorForm<String> {
public String id;
@NotEmpty
public String name;
@NotNull
public BigDecimal price;
public ProductStatus status;
private final ProductService service;
public ProductEditor(ProductService service) {
this.service = service;
}
@Override
public String id() {
return id;
}
@Override
public void save(HttpRequest httpRequest) {
service.update(id, name, price, status);
}
}

When save() is called, the form fields have already been hydrated from the browser state — you can read them directly:

@Override
public void save(HttpRequest httpRequest) {
// 'name', 'price', 'status' already contain the values the user edited
var entity = new Product(id, name, price, status);
repository.save(entity);
}

The creation form interface. Mateu calls create() when the user submits the create form. It returns the id of the newly created entity, which the framework uses to navigate to the detail view.

public interface CrudCreationForm<IdType> {
IdType create(HttpRequest httpRequest);
}
MethodCalled whenMust do
create(httpRequest)User clicks Save in the create formPersist the new entity and return its id
public class ProductCreationForm implements CrudCreationForm<String> {
@NotEmpty
public String name;
@NotNull
public BigDecimal price;
private final ProductService service;
public ProductCreationForm(ProductService service) {
this.service = service;
}
@Override
public String create(HttpRequest httpRequest) {
return service.create(name, price);
}
}

A common shortcut is implementing both interfaces on a single class, using a nullable id field to distinguish create from edit:

public class ProductForm
implements Identifiable, CrudEditorForm<String>, CrudCreationForm<String> {
@EditableOnlyWhenCreating
public String id;
@NotEmpty
public String name;
@NotNull
public BigDecimal price;
public ProductStatus status;
@Override
public String id() { return id; }
@Override
public void save(HttpRequest httpRequest) {
service.update(id, name, price, status);
}
@Override
public String create(HttpRequest httpRequest) {
return service.create(name, price, status);
}
}

The orchestrator then uses this class for both Editor and CreationForm:

public class ProductOrchestrator
extends Crud<ProductView, ProductForm, ProductForm, ProductFilters, ProductRow, String> {
...
@Override public Class<ProductForm> editorClass() { return ProductForm.class; }
@Override public Class<ProductForm> creationFormClass() { return ProductForm.class; }
}

When the entity itself is the form (no separate editor class), use AutoNamedView<T> in the adapter rather than creating a dedicated form class. AutoNamedView implements both CrudEditorForm and CrudCreationForm automatically via its save() and create() methods, which delegate to the CrudRepository.

This is what AutoCrud does internally — it wraps the entity in AutoNamedView so you never have to write the form interfaces yourself.

This is handled automatically by AutoCrud and FilteredAutoCrud — you only need these interfaces when using Crud directly with separate types for view, editor, and creation form.


  • Full control with Crud — how these interfaces fit into the six-type-parameter orchestrator
  • CrudAdapter — where getEditor() and getCreationForm() return instances of these interfaces