Developer Guide
This section covers the data model, configuration reference, and extension points available to developers integrating User Profile into an application.
Data Model
User Profile stores one record per user. The entity is intentionally simple: a flat table keyed by a generated integer identifier and associated to its owner by username.
---
config:
layout: elk
---
erDiagram
up_user_profile {
integer id PK
varchar username UK
varchar first_name
varchar last_name
varchar phone_number
varchar address
varchar email
blob avatar
timestamp creation_time
}
Database Schema
User Profile manages a single table. The schema below represents the DDL generated by Hibernate for a standard relational database. Column names follow Hibernate's default snake_case naming strategy.
CREATE SEQUENCE up_user_profile_seq
START WITH 1
INCREMENT BY 50;
CREATE TABLE up_user_profile (
id INTEGER NOT NULL,
username VARCHAR(255) NOT NULL UNIQUE,
first_name VARCHAR(255),
last_name VARCHAR(255),
phone_number VARCHAR(255),
address VARCHAR(255),
email VARCHAR(255),
avatar BLOB,
creation_time TIMESTAMP(6),
CONSTRAINT pk_up_user_profile PRIMARY KEY (id)
);
The avatar column stores raw binary image data. Its exact SQL type depends on the database dialect: BLOB on MySQL and H2, BYTEA on PostgreSQL.
The up_user_profile_seq sequence is created by Hibernate when GenerationType.AUTO resolves to a sequence-based strategy, which is the default behaviour in Hibernate 7.
The username column carries a NOT NULL UNIQUE constraint, so the database itself guarantees that a profile belongs to exactly one user. An application upgrading from a version that allowed duplicates must remove them before the constraint can be created.
Module Overview
User Profile is structured as six Maven modules following the AppJars layered architecture:
| Module | Artifact ID | Description |
|---|---|---|
| Model | appjars-user-profile-model |
DTOs, filter and sort classes, auto-configuration |
| Business API | appjars-user-profile-business |
UserProfileService interface |
| Business Impl | appjars-user-profile-business-impl |
Service implementation with @Transactional operations |
| Data API | appjars-user-profile-data |
UserProfileDao interface |
| Data Impl | appjars-user-profile-data-impl |
JPA entity and DAO implementation |
| Flow UI | appjars-user-profile-flow |
Vaadin views, forms, and route configuration |
A monolithic application includes the three implementation modules (-business-impl, -data-impl, -flow). The API modules (-business, -data) are pulled in transitively.
Note
Version 2.0 standardises explicit serialVersionUID values on the serializable DTO and Vaadin state types. Sessions, caches, or messages serialized by an earlier version are not compatible and must not be carried across the upgrade.
Spring Auto-Configuration
User Profile registers itself through Spring Boot's auto-configuration mechanism. The entry point is UserProfileAutoConfiguration, declared in:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
This class scans the com.appjars.userprofile package for all Spring components and JPA entities, so no additional @EntityScan or @ComponentScan declarations are required in the host application beyond those described in the Getting Started guide.
Configuration Properties
The following properties can be set in application.properties to customise the behaviour of User Profile:
| Property | Default | Description |
|---|---|---|
com.appjars.userprofile.url.profile |
up/profile |
URL path of the personal profile view |
com.appjars.userprofile.url.profile-list |
up/profile-list |
URL path of the administrator profiles list |
com.appjars.userprofile.feat.redirect |
true |
When true, users without a profile are automatically redirected to the profile view on their first login |
spring.servlet.multipart.max-file-size |
1MB |
Maximum size of individual avatar image uploads. Set to 10MB to match the limit the profile form itself enforces |
spring.servlet.multipart.max-request-size |
10MB |
Maximum size of multipart requests. Should match max-file-size |
The profile form rejects any image above 10 MB before it reaches the server, so raising the multipart limits beyond 10MB has no effect.
Service API
UserProfileService is the primary programmatic interface to User Profile. It extends CrudService and ValidationSupport<UserProfileDto>, and exposes the following methods specific to the module:
// Find a profile by its exact, case-sensitive username
Optional<UserProfileDto> findByUsername(String username);
// Retrieve a paginated, filtered, and sorted stream of profiles
Stream<UserProfileDto> getProfiles(int offset, int limit, ProfileFilter filter, List<ProfileSort> sortOrder);
// Count profiles matching a filter
Integer countProfiles(int offset, int limit, ProfileFilter filter);
findByUsername matches the username exactly and case-sensitively; it does not treat the value as a pattern, so _ and % carry no special meaning. It returns an empty Optional for a null username.
Inject UserProfileService in any Spring-managed component to read or write profiles programmatically:
@Autowired
private UserProfileService userProfileService;
public Optional<UserProfileDto> getCurrentUserProfile(String username) {
return userProfileService.findByUsername(username);
}
Username Uniqueness
A username identifies exactly one profile. The rule is enforced at two levels:
- The
up_user_profile.usernamecolumn carries aNOT NULL UNIQUEconstraint. UserProfileServicecontributes a creation validator and an update validator throughValidationSupport.getValidators(). Saving a profile whose username already belongs to a different profile raises acom.flowingcode.backendcore.validation.ValidationExceptioncarrying the message keyappjars.userprofile.profileview.username.alreadyexists.
Both views catch the exception and show the translated message as a notification. A custom caller of the service API must handle it as well:
try {
userProfileService.save(profile);
} catch (ValidationException e) {
String messageKey = e.getErrors().get(0).getMessageKey();
// present the translated message to the user
}
Events
UserProfileService publishes a ProfileUpdatedEvent Spring application event each time a profile is created or updated. Subscribe to it in any Spring component using @EventListener:
@Component
public class ProfileSyncListener {
@EventListener
public void onProfileUpdated(ProfileUpdatedEvent event) {
String username = event.username();
String firstName = event.firstName();
String lastName = event.lastName();
String email = event.email();
// custom logic: update search index, sync to external system, etc.
}
}
The event is a Java record and carries the username, first name, last name, and email of the updated profile. No event is published on deletion.
Free Mode Enforcement
Without a valid User Profile license, the appjar runs in free mode and limits the application to five profiles. The limit is enforced in UserProfileServiceImpl, which throws a FreeLimitReachedException when save is called and five profiles already exist, or when update is called and the count is above five. Both views catch the exception and display an alert, so a custom caller of the service API must handle it as well.
The views also reflect the limit before the operation is attempted: while no license is present they display a restrictions bar with a Profiles usage badge, and they disable the New profile button and the Save button of the profile form once the limit is reached. In addition, AuthenticationListener suppresses the post-login redirect while the limit is reached, so that users without a profile are not sent to a view where they cannot save one.
Above the allowance the views enter a stricter state. When more than five profiles exist — the situation an application lands in when a license lapses — the Edit row action of the profiles list and the Save button of an existing profile are disabled as well, so the only operation left is deletion until the count is back within the allowance.
Customisation
Replacing Username Validation
The username entered in the profile form is validated by a UsernameValidator bean, a Vaadin Validator<String>. The default implementation, DefaultUsernameValidator, requires between 4 and 50 characters, rejects values containing spaces, and additionally applies email validation when the value contains an @ character.
The bean is registered with @ConditionalOnMissingBean, so declaring a UsernameValidator in the application replaces it:
@Bean
UsernameValidator usernameValidator() {
return (value, context) -> value != null && value.startsWith("emp-")
? ValidationResult.ok()
: ValidationResult.error("Username must start with 'emp-'");
}
Providing Profile Data from Another Source
Other AppJars obtain the first name, last name, and profile picture of a user through the UserProfileProvider interface. User Profile registers DefaultUserProfileProvider, which resolves that data through UserProfileService and maps the profile avatar to the profile picture.
The bean is registered with @ConditionalOnMissingBean, so an application that stores this information elsewhere — an LDAP directory, an external identity provider, a legacy table — can declare its own implementation:
@Bean
UserProfileProvider ldapUserProfileProvider(LdapTemplate ldapTemplate) {
return username -> ...;
}
The interface returns the shared com.appjars.model.utils.UserProfileDto, which carries only the first name, last name, and profile picture. It is not the same class as the UserProfileDto of this appjar.
Assigning a Router Layout
By default, User Profile registers its views without a parent layout. To wrap them in the application's main layout, inject the RouteConfigurer bean and call setViewsRouterLayout in a @PostConstruct method:
@Autowired
private RouteConfigurer routeConfigurer;
@PostConstruct
public void configure() {
routeConfigurer.setViewsRouterLayout(MainLayout.class);
}
Customising View URLs
The default URL paths can be overridden in application.properties:
com.appjars.userprofile.url.profile=myapp/my-profile
com.appjars.userprofile.url.profile-list=myapp/manage-profiles
Disabling the Profile Redirect
When com.appjars.userprofile.feat.redirect=true (the default), users who have not yet created a profile are automatically redirected to the My Profile view after login. To disable this behaviour:
com.appjars.userprofile.feat.redirect=false