Skip to content

Customization

AppJars are designed to work out of the box, but real applications routinely need to go beyond the built-in behaviour. This page describes the main extension points available across all AppJars.

Extending the Data Model

AppJar data models are designed to cover the common requirements of their domain, but real applications often need to store additional data alongside the built-in fields. Because AppJar entities are standard JPA classes managed by Hibernate, there is a straightforward technique for adding columns to an AppJar table without forking the source or maintaining a patch: placing a replacement class at the same fully-qualified class name in the application's own source tree.

The FQN Override Technique

When the JVM and Hibernate scan the classpath, the application's own compiled classes take precedence over those packaged inside dependency JARs. If the application defines a class with the exact same package and name as an AppJar entity, that class is loaded instead of the original. Hibernate then creates or alters the table to match the replacement class, including any additional fields it declares.

The replacement class must:

  1. Extend the original entity class (to inherit all existing mappings) — or, if the goal is a complete replacement, reproduce all original field mappings.
  2. Carry the same @Entity and @Table annotations so that Hibernate associates it with the correct table.
  3. Be located in a package that is included in the application's entity scan.

Example: Adding a Department Field to User Manager

Suppose the application needs to store a department name alongside each user. The User Manager entity is com.appjars.usermanager.entities.UserEntity. Create the following class in the application source tree at the path src/main/java/com/appjars/usermanager/entities/UserEntity.java:

package com.appjars.usermanager.entities;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;

@Entity
@Table(name = "um_user")
public class UserEntity extends com.appjars.usermanager.entities.UserEntity {

    @Column(name = "department")
    private String department;

    public String getDepartment() {
        return department;
    }

    public void setDepartment(String department) {
        this.department = department;
    }
}

When the application starts, Hibernate discovers this class on the application classpath before the one packaged in the AppJar JAR. The um_user table gains a department column. All existing User Manager behaviour — service methods, DAO queries, views — continues to work unchanged because the replacement class inherits all original mappings.

Accessing the Extended Data

Because the replacement entity class is defined in the application, it is only accessible directly through application code. The AppJar's service and DAO layer continues to operate on the original DTO and entity types; it is unaware of the extension. To read or write the extended fields, query the entity directly through a custom Spring Data repository or a custom DAO:

public interface ExtendedUserRepository extends JpaRepository<UserEntity, Integer> {
    Optional<UserEntity> findByUsername(String username);
}
@Autowired
private ExtendedUserRepository extendedUserRepository;

public void assignDepartment(String username, String department) {
    extendedUserRepository.findByUsername(username).ifPresent(user -> {
        user.setDepartment(department);
        extendedUserRepository.save(user);
    });
}

The standard AppJar UserService and its views remain unaffected and continue to function as documented.

Considerations

Schema migrations — If the application uses a migration tool such as Flyway or Liquibase, the additional column must be added via a migration script rather than relying solely on Hibernate's ddl-auto. This is recommended for any production environment.

AppJar updates — When an AppJar version adds a new field or renames an existing one in the original entity, the replacement class must be reviewed and updated to stay compatible. Check the AppJar release notes when upgrading.

Circular class loading — The replacement class cannot extend itself. If the goal is to add fields to com.appjars.foo.entities.FooEntity, the replacement must extend a class in a different package or reproduce all original fields manually. Using a superclass from a different package that holds the original fields avoids this issue.

DTO propagation — The AppJar's DTO class is separate from the entity class. Extended fields are not automatically included in DTOs returned by AppJar services. To expose extension fields through the service layer, create a custom service or DAO that works with the extended entity directly.

Extending the Service Layer

AppJar service beans are standard Spring components. Because they are the authoritative @Primary bean for their interface, they can be replaced by declaring a custom bean that extends the AppJar implementation and is annotated with @Primary:

@Primary
@Service
public class CustomUserService extends UserServiceImpl {

    @Override
    public UserDto save(UserDto user) {
        // custom pre-save logic
        UserDto saved = super.save(user);
        // custom post-save logic (e.g. publish an event, call an external system)
        return saved;
    }
}

This approach allows adding cross-cutting concerns — event publishing, audit hooks, external system notifications, custom validation — without touching any AppJar source. All built-in behaviour is preserved through the super call.

Note

The custom service class must be in a package scanned by the application's component scan, and must carry @Primary to take precedence over the AppJar's own service bean.

AppJar views are registered as standard Vaadin routes with configurable URL paths. Application code can navigate to any AppJar view using the standard Vaadin navigation API:

UI.getCurrent().navigate("users");         // navigate to User Manager's user list
UI.getCurrent().navigate("activity-log");  // navigate to Activity Log

This makes it straightforward to add buttons or links in your own application views that open AppJar views directly — for example, a "Manage Users" link in an admin dashboard that navigates to User Manager's users list, or a "View Logs" link that opens Activity Log.

Route paths are configurable through application.properties. The complete list of property keys and default paths for every AppJar is documented in Configuring View Route Paths. When paths are overridden, update any navigation calls in application code accordingly.