1. Project setup#

What this step does. Starts a plain SpringBoot project, adds one Palmyra dependency, configures a database, and creates two empty tables. No Palmyra-specific code runs yet — that lands in steps 2 and 3.

build.gradle#

plugins {
    id 'org.springframework.boot'          version '3.3.4'
    id 'io.spring.dependency-management'   version '1.1.6'
    id 'java'
    id 'application'
}

java { toolchain { languageVersion = JavaLanguageVersion.of(21) } }
application { mainClass = 'com.example.empmgmt.AppMain' }

repositories {
    mavenCentral()
    maven { url 'https://repo.palmyralabs.com/releases' }
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'com.palmyralabs.palmyra:palmyra-spring:1.4.4'

    compileOnly         'org.projectlombok:lombok:1.18.34'
    annotationProcessor 'org.projectlombok:lombok:1.18.34'

    runtimeOnly         'org.mariadb.jdbc:mariadb-java-client:3.4.0'
}

SpringBoot entrypoint#

package com.example.empmgmt;

@SpringBootApplication
@Import(PalmyraSpringConfiguration.class)
@EnableJpaRepositories
@EntityScan
public class AppMain {
    public static void main(String[] args) { SpringApplication.run(AppMain.class, args); }
}

application.yaml#

spring:
  jpa:
    show-sql: true
    hibernate: { ddl-auto: validate }
  datasource:
    url: jdbc:mariadb://localhost:3306/empmgmt
    username: empmgmt
    password: empmgmt
    driverClassName: org.mariadb.jdbc.Driver

server:
  port: 8080
  servlet: { context-path: /api }

Database tables#

CREATE TABLE department (
  id          BIGINT AUTO_INCREMENT PRIMARY KEY,
  code        VARCHAR(32)  NOT NULL UNIQUE,
  name        VARCHAR(128) NOT NULL,
  description VARCHAR(512)
);

CREATE TABLE employee (
  id             BIGINT AUTO_INCREMENT PRIMARY KEY,
  login_name     VARCHAR(128) NOT NULL UNIQUE,
  first_name     VARCHAR(64)  NOT NULL,
  last_name      VARCHAR(64)  NOT NULL,
  department_id  BIGINT       NOT NULL,
  joining_date   DATE         NOT NULL,
  status         VARCHAR(16)  NOT NULL DEFAULT 'ACTIVE',
  CONSTRAINT fk_employee_department FOREIGN KEY (department_id) REFERENCES department(id)
);

Run ./gradlew bootRun. The app should boot on :8080 with context path /api. Nothing answers yet — we add models and handlers next.

Source layout#

src/main/java/com/example/empmgmt/
  AppMain.java
  entity/
    DepartmentEntity.java
    EmployeeEntity.java
  model/
    DepartmentModel.java
    EmployeeModel.java
  handler/
    DepartmentHandler.java
    EmployeeHandler.java

Same three-file split as the clinic reference (JPA entity, Palmyra model, handler). If you only want Palmyra to own persistence, the entity/ folder is optional — we keep it for clean separation.

Variations#

  • Different database? Swap the driver coordinate + JDBC URL. For PostgreSQL:

    runtimeOnly 'org.postgresql:postgresql:42.7.3'
    datasource:
      url: jdbc:postgresql://localhost:5432/empmgmt
      driverClassName: org.postgresql.Driver

    Palmyra auto-detects the dialect from the connection — no extra configuration. The full list of supported databases is in Requirements.

  • No existing database? Palmyra plays fine with H2 in-memory for local development — change the URL to jdbc:h2:mem:empmgmt;DB_CLOSE_DELAY=-1, drop in the H2 runtime dependency, and point ddl-auto at create for the first run so the tables bootstrap themselves.

  • No separate JPA entity? Skip the entity/ folder entirely and let the Palmyra model be the only Java representation of each table. The rest of the tutorial works unchanged; you give up JPA’s @ManyToOne / cascades in return for a thinner codebase.