Let’s set the scene: you’re working on a line-of-business application within your enterprise. It has to handle a complicated domain, and the business expect it to deliver value for them for many years to come. How would you go about building it?
You’ll agree I expect that establishing a tight feedback loop with your domain experts will be crucial, but how best to do that? You might think about using behaviour driven design (BDD), but depending on the sophistication of your end-users, that isn’t an approach that will work in all contexts.
Another approach would be to use wireframes or mockups of the UI, and those too are useful for some contexts, but they don’t demonstrate behaviour and there is the risk of bikeshedding (focusing on the presentation concerns, ignoring the more important domain concepts that lie below).
Obtaining Feedback by building the UI
Alternatively, you might decide that the best way to elicit meaningful feedback is by actually building out the app, although that is not without its own pitfalls. Among the many decisions you’ll need to make, one is figuring out how to develop the UI.
If you’re a fan of front-end frameworks, you might reach for something like React or Angular, with corresponding controllers for the backend. Or if you prefer Java over Javascript, with the UI defined by the server, you might use something like Vaadin or HTMX.
To speed you up, you might also decide to code generate your app, for example using JHipster. Or you might let AI agent do the code generation for you (though you run the risk of not understanding the resultant code).
Costs & Risks
Whichever UI stack you choose, you’ll need to keep tight control; by some estimates, building the UI consumes a substantial slice of the development budget, perhaps even as high as 40%.
And there’s a much more subtle cost to building a UI in this way, which is this: without care, your custom-written presentation layer can act as a kind of distortion force field, obscuring and obfuscating the underlying domain concepts of your application.
If you have a frontend UI team separate from the backend teams, then the risk is even higher, because of the intrinsic difficulty of explaining subtle concepts and ideas to another team who have their own priorities. It can happen though even if there’s just one team with responsibility for both domain and UI; there’s always the risk that the two layers drift apart.
Naked what, now?
Apache Causeway solves the issue of UI development cost, while also helping you develop a good domain model. And it does this by the simple idea of generating the UI for you, as a projection of the underlying domain objects.
But Causeway isn’t a compile-time generation tool (such as JHipster); instead, the UI is generated at run-time, as a projection of the underlying domain model. This architectural style is called naked objects; the structure and behaviour of the domain objects is reflected directly into the UI.
It’s somewhat analogous to how an ORM such as Hibernate operates. Hibernate works by building a metamodel at runtime of the structure of the domain entities, and it then uses that to submit SQL on the fly. Similarly, Causeway builds up its own metamodel, and uses that metamodel to render the UI (or API).
NOTE
By the way, a human-usable UI is only one such projection of the metamodel – Causeway can also use it to dynamically generate a REST API for your app (along with Swagger files), as well as a complete GraphQL API.
In the real world
You might be sceptical that such an approach can be used to build anything other than toy or CRUD-style applications. By way of counterexamples, I can tell you that the main benefit administration system for the Irish Central Government — used by 7,000 officers across the country, who administer over 40 benefits (pension, child benefit etc) as well as the main identity database for the citizens of Ireland — uses the naked objects approach. That app has nearly 3000 domain entities, is 20 years old and is still going strong.
For another example, Apache Causeway has been used to develop Estatio, an in-house ERP system for Eurocommercial Properties, who own and operate 25 shopping centres across Europe. Estatio isn’t quite as old as the Irish system, 13 years in fact, but it’s in rude health. It consists of about 300 domain concepts, is used by about 200+ users, and processes approx. €500m invoices each year (though it’s more than just an invoicing system). Moreover, Estatio is developed and supported by a team of just 3 developers (actually, not even; only one of us is full-time).
Show me the money
Enough scene-setting; we’ll spend the rest of this article learning what an Apache Causeway application looks like. To do that, we’ll use a version of the well-known petclinic app. Causeway has a tutorial that builds up this app over several lessons. But for now, we’ll start with the final version of the app and explore the already implemented features. I’ve pushed our version of the app to a repo on github.
First, make sure you have the prerequisites:
- Java 17+
- Apache Maven 3.9.9+ and/or mvndaemon (
mvnd). - git
- a Java IDE (IntelliJ IDEA, Eclipse, VSCode etc)
Create an empty directory, and in it clone the repo and checkout the main branch, then build the app:
git clone https://github.com/danhaywood/petclinic-javapro .
git checkout main
mvn clean install -T1C # or mvnd clean install
Wait for the internet to be downloaded, and then open up the app in your IDE.
Running the app
Let’s start by running the app. Locate the PetClinicApp, you’ll notice that this is a Spring Boot application; Causeway is built on top of Spring Boot. The class contains a main() method, so you can start it immediately.
Or, if you want to run from the command line, use:
mvn spring-boot:run -pl webapp
If you open up http://localhost:8080, you’ll see:

Click on the “Generic UI (Wicket)”, which will take you to http://localhost:8080/wicket:

Use sven/pass as the username/password (details are also on the splash page previously). You should end up at the home page:

Click on the icon for “Jamal Washington”, and you’ll be viewing the page for this pet owner:

You’ve already encountered your first two domain objects: the HomePageViewModel (a view model object), and PetOwner (an entity).
Both of the pages you visited were rendered dynamically and directly from the underlying domain object; there is no custom or generated controllers, templates or Javascript involved.
Let’s now find out how the pages you’ve seen rendered relate to the code.
Domain Objects: exploring the PetOwner class
Locate the PetOwner.java class in your IDE, and navigate to its definition:
@Entity
@Table(schema= PetOwnerModule.SCHEMA,
uniqueConstraints = {@UniqueConstraint(name = "PetOwner__name__UNQ", columnNames = {"name"})})
@EntityListeners(CausewayEntityListener.class)
@Named(PetOwnerModule.NAMESPACE + ".PetOwner")
@DomainObject
@NoArgsConstructor(access = AccessLevel.PUBLIC)
@XmlJavaTypeAdapter(PersistentEntityAdapter.class)
@ToString(onlyExplicitlyIncluded = true)
public class PetOwner implements Comparable<PetOwner> {
...
}
Of these various annotations, its the @DomainObject annotation that’s significant here, indicating that this class should be included in Causeway’s metamodel.
Let’s take a closer look at the members of this class.
Properties
Using your IDE, search for the getters of PetOwner class:

These instance fields are all rendered automatically in the UI.
Most of these are scalars, returning strings, numbers, dates etc., and so most are rendered as text boxes of various hues (although content renders a PDF).
We call these properties.
For example, here’s the knownAs property:
@Property(editing = Editing.ENABLED)
@Column(length = 40, nullable = true, name = "knownAs")
@Getter @Setter
private String knownAs;
The @Property annotation comes from Causeway; you can see that it allows editing, meaning that the value of this property can be modified directly.
Try it in the application.
Collections
The pets field on the other hand returns a collection type (Set<Pet>), and so it is rendered as a table.
We call these collections:
@Collection
@OneToMany(mappedBy = "petOwner", cascade = CascadeType.ALL, orphanRemoval = true)
@Getter
private Set<Pet> pets = new TreeSet<>();
NOTE
You might also have noticed that in the UI that there is also avisitscollection that does not appear in thePetOwnerentity. We’ll look at this in a later article.
Actions
Unlike the knownAs property, if you try to edit the name property you won’t be able to, because Causeway’s default is that properties are read-only.
Instead, the application allows the name to be modified using an “action”.
More generally, any non-trivial change in state is implemented in this way.
Update Name
For example, the “update name” action is implemented using the updateName(...) method:
@Action(semantics = IDEMPOTENT)
@ActionLayout(
describedAs = "Updates the name of this object, certain characters (" +
Name.PROHIBITED_CHARACTERS + ") are not allowed.")
public PetOwner updateName(
@Name final String name) {
setName(name);
return this;
}
@MemberSupport public String default0UpdateName() {
return getName();
}
Note that the name parameter is annotated with the @Name meta-annotation, which provide for a means of declaratively defining validation rules on a parameter:
...
@Parameter(maxLength = Name.MAX_LEN, mustSatisfy = Name.Spec.class)
...
public @interface Name {
int MAX_LEN = 40;
String PROHIBITED_CHARACTERS = "&%$!";
class Spec extends AbstractSpecification<String> {
@Override public String satisfiesSafely(String candidate) {
for (char prohibitedCharacter : PROHIBITED_CHARACTERS.toCharArray()) {
if( candidate.contains(""+prohibitedCharacter)) {
return "Character '" + prohibitedCharacter + "' is not allowed.";
}
}
return null;
}
}
}
The framework ensures that the supplied argument satisfies these rules:

This action is also supported by the default0UpdateName() method.
This is a convenience for the end-user, providing a default value for the 0th parameter of this action (in other words, the name parameter) when its action prompt is brought up.
Add Pet
Another example of an action is “Add Pet”:
@Action
@ActionLayout(associateWith = "pets", sequence = "1")
public PetOwner addPet(@PetName final String name, final PetSpecies species) {
pets.add(new Pet(this, name, species));
return this;
}
@MemberSupport
public String validate0AddPet(final String name) {
if (getPets().stream().anyMatch(x -> Objects.equals(x.getName(), name))) {
return "This owner already has a pet called '" + name + "'";
}
return null;
}
With this action we can see that validation can be also defined imperatively in the supporting validate0AddPet method.
If this method returns a non-null value, then this is displayed to the user:

Admittedly neither of the above actions do very much; they are rather “CRUD-y”. Then again, this is just a simple demo app. But hopefully you can see that the business logic in these actions could be arbitrarily complicated. For example, in Estatio (mentioned above) we have an action to perform the quarterly invoice run for all tenants of a shopping center.
The PetOwner class is an entity, while the HomePageViewModel is a view model.
If you locate the HomePageViewModel.java, you’ll see that it too is annotated with @DomainObject, and uses the same conventions to identify its members (two @Collections, in this case).
The difference between an entity and a view model is persistence: the entity’s state is stored in the database, while a view model is either stateless (as is the case for HomePageViewModel) or else its state is inferred, ultimately, from its URL.
But domain entities and view models aren’t the only types of objects automatically rendered by Causeway; there is one more: the domain service.
Domain Services
In the menu bar at the top you’ll find the “Pet Owners” menu:

Causeway builds this menu bar from classes annotated with @DomainService.
For this app there is just one, PetOwners.
@Named(PetOwnerModule.NAMESPACE + ".PetOwners")
@DomainService
@Priority(PriorityPrecedence.EARLY)
@RequiredArgsConstructor(onConstructor_ = {@Inject} )
public class PetOwners {
...
}
The @DomainService annotation is a meta-annotation, so if you drill in you’ll see that this is also a Spring singleton service. If you inspect the class’ members, meanwhile, then you’ll find a number of actions, implemented in the same way as domain objects. However, whereas with a domain object actions are rendered as a button on the object’s page, for a domain service the actions appear in the menu bar.
Most of these actions delegate to PetOwnerRepository, for example:
@Action(semantics = SemanticsOf.SAFE)
@ActionLayout(promptStyle = PromptStyle.DIALOG_SIDEBAR)
public List<PetOwner> findByName(
@Name final String name
) {
return petOwnerRepository.findByNameContaining(name);
}
The PetOwnerRepository is just a regular Spring repository service; you are free to use the usual Spring framework features within your Causeway application.
The “Create” action in PetOwners on the other hand delegates to the RepositoryService, a lower-level service provided by Causeway itself that can be used to persist (save) domain objects:
@Action(semantics = SemanticsOf.NON_IDEMPOTENT)
public PetOwner create(
@Name final String name,
@Parameter(maxLength = 40, optionality = Optionality.OPTIONAL)
final String knownAs,
@PhoneNumber
final String telephoneNumber,
@Parameter(optionality = Optionality.OPTIONAL)
final EmailAddress emailAddress) {
final var petOwner = PetOwner.withName(name);
petOwner.setKnownAs(knownAs);
petOwner.setTelephoneNumber(telephoneNumber);
petOwner.setEmailAddress(emailAddress);
return repositoryService.persist(petOwner);
}
TIP
Notice that the@Nameannotation is once more used for thenameparameter, meaning that the declarative validation rules for name that we saw earlier are automatically applied here.
Over on the right hand side of the menubar there are some further menus; these are provided by the framework itself.
In fact, if you logout and then log back in as the secman-admin/pass administrator, then you’ll find a bunch more services provided by the framework, mostly for cross cutting concerns such as security and auditing:

By now you will hopefully appreciate Causeway’s strong focus on the domain, with state and behaviour of the domain objects represented directly in the UI.
As you gain confidence that your domain model is solid, you will though want to start adding some polish to your app’s UI. So let’s now learn about some of the support that Causeway provides for you to do this.
UI Hints
In our introduction we claimed that the UI generated by Causeway is a projection of the underlying domain objects. But then, you might be asking, how does Causeway know how to use columns, tabs and fieldsets to render the page?
OK, so it’s true, we simplified a little.
Locate the PetOwner.java file in your IDE, and have a look at the files close by:

As well as the PetOwner, Pets and PetOwnerRepository classes that we’ve seen already, there are some additional files.
The PetOwner.png file provides the icon for our PetOwners, the .columnOrder.txt files determine the order in the properties of objects appear in tables, and the .layout.xml file is what tells Causeway to layout the object’s members across various columns and tabs.
But here’s the important point: none of these these UI files actually need to exist; the app will still work without them. To test this, try temporarily renaming or deleting them: the framework will still render the objects using a reasonable default layout. Contrast this with a UI-first approach, where the page is written first and then is bound to the domain object.
With Causeway, you always focus on the domain object. Finessing the UI comes later, once you’ve figured out with your domain experts what it is you’re trying to build.
Making a change
To get an idea of how quick it is to develop with Causeway, let’s make a small change to PetOwner, by adding a what3words property to capture their address.
TIP
Usegit reset --hard origin/mainto discard any changes you might have made so far.
In the PetOwner class, add the following field:
@Property(editing = Editing.ENABLED)
@PropertyLayout(
named = "what3words",
describedAs = "A 3-word address, eg '///apple.banana.orange'")
@Column(length = 64, nullable = true, name = "what3words")
@Getter @Setter
private String what3words;
And, optionally, you can also update the PetOwner.layout.xml file to indicate that this new field should be rendered near to the existing contact details:
...
<bs3:col span="12">
<cpt:fieldSet name="Contact" id="contact">
<cpt:property id="telephoneNumber"/>
<cpt:property id="emailAddress"/>
<cpt:property id="what3words"/>
</cpt:fieldSet>
</bs3:col>
...
Run the application and the new field should be visible, and editable.
As an optional exercise, use a validate method on the property to ensure that the candidate value is valid:
@MemberSupport
public String validateWhat3words(String proposed) {
return proposed == null || proposed.startsWith("///")
? null
: "Must start with '///' (e.g. '///apple.banana.orange')";
}
I’ll leave it to you to flesh this out more completely.
TIP
Usegit reset --hard javapro/an-introduction/making-a-change/solutionto discard any local changes and switch to the above code.
You might be wondering whether additional changes are required for the persistence layer; after all PetOwner is a JPA entity, so there is a database table somewhere? Well, how this works is that in prototype mode the app is running against an in-memory H2 database, which is recreated each time the app is started, and reseeded with fixture data. (We’ll be looking at fixtures in the next article in this series).
In a production context you would likely use Flyway or Liquibase, both of which have integrations with Spring Boot. When enabled, these libraries perform any SQL migrations to the database schema first, after which the rest of the application is bootstrapped.
Internals: Metamodels
To finish off this introduction to Apache Causeway, let’s take a quick look at the metamodel that the framework builds up in order to render the UI.
Navigate to the “Metadata” tab of the PetOwner‘s page, and then click on “Inspect MetaModel”

This will open up a page with a tree view:

At the root of the tree is the domain class (PetOwner), and then the next node down is “Facets” which contains metadata pertaining to the domain class itself, for example EntityFacet.
After that is a node for each action, property and collection of the class; and each of these has a collection of facets also.
Wrapping up
In this article we’ve learnt how Apache Causeway dynamically generates the UI for a Spring Boot app, rendering each domain object on its own page. We’ve also seen a little about how the domain object members (properties, collections and actions) can be supplemented with supporting validation and usability hints, with validation defined either declaratively or imperatively.
I hope you’ve enjoyed reading this article and trying out the Apache Causeway framework; look out for further articles in upcoming issues.
Useful links:
- Repo for this article
- Apache Causeway docs
- Apache Causeway docs – properties, collections and actions
- Apache Causeway docs – domain services
- Apache Causeway docs – layout files
- Apache Causeway docs – Petclinic tutorial (if you want to build the app shown here from scratch).