Java Helidon
GitHubOfficialFirst committed by thesurenkDistributed by GitHub
- Skill
- Guidelines
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides comprehensive guidelines for building robust applications using Helidon 4, covering both Helidon SE and Helidon MP programming models. It includes critical migration information for transitioning from Helidon 3 to 4, project structure recommendations, and architectural patterns for web, service, and data layers. The content emphasizes modern Java 21+ practices, such as leveraging virtual threads in Helidon SE and standard Jakarta EE/MicroProfile APIs in Helidon MP, while ensuring security, observability, and testability.
What it does
- Provides a mapping table for Helidon 3 to 4 API changes to prevent compilation errors.
- Outlines project structure and dependency management best practices for Helidon applications.
- Offers architectural patterns for separating business logic from web and persistence layers.
- Details configuration management, including externalization and secrets handling.
- Provides code examples for implementing services, repositories, and routing in both SE and MP.
- Covers testing strategies, observability, and security implementation standards.
When to use it
- When starting a new Helidon 4 project or migrating an existing Helidon 3 application.
- When you need to resolve common Helidon 4 compilation errors related to API changes.
- When designing the service or data layer of a Helidon-based microservice.
- When implementing security, metrics, or health checks in a Helidon application.
- When writing unit or integration tests for Helidon SE or MP components.
Advantages
- Reduces development friction by highlighting common Helidon 4 pitfalls.
- Promotes clean, maintainable code through clear separation of concerns.
- Ensures alignment with modern Java 21+ features and Helidon-specific idioms.
- Helps maintain consistent project structure and dependency management.
Usage examples
- “How do I handle database row mapping in Helidon 4 SE?”
- The agent provides an example of using DbClient with parameterized statements and explicit column mapping, noting the change in how column accessors return values.
- “What is the correct way to test a Helidon MP resource?”
- The agent explains the use of the @HelidonTest annotation and the correct artifact coordinates for the testing module in Helidon 4.
- “How should I structure my Helidon SE project?”
- The agent recommends organizing code by feature or domain rather than technical layer and provides guidance on explicit dependency injection.
- “Why is my Helidon 4 code failing to compile after migrating from Helidon 3?”
- The agent compares the provided code against the Helidon 3 to 4 API change table to identify deprecated methods or renamed packages.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/github/java-helidon/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip java-helidon.zip -d .agents/skills
Documentation by thesurenk
Helidon Best Practices
Your goal is to help me write high-quality Helidon applications by following established best practices.
Helidon 3 → 4 API changes
Helidon 4 renamed or resignatured APIs that appear widely in their Helidon 3 form. The left column does not compile on Helidon 4. Check generated code against this table before returning it.
| Do not use (Helidon 3) | Use (Helidon 4) |
|---|---|
io.helidon.common.http.Http.Status | io.helidon.http.Status |
io.helidon.webserver.Service | io.helidon.webserver.http.HttpService |
Routing.Rules, update(Routing.Rules) | HttpRules, routing(HttpRules) |
request.path().param("id") | request.path().pathParameters().get("id") |
String s = column.as(String.class) | column.getString() or column.get(String.class) |
dbClient.execute(exec -> ...) returning Single/Multi | dbClient.execute() returning Optional<DbRow> / Stream<DbRow> |
javax.* | jakarta.* |
helidon-microprofile-tests-junit5 | helidon-microprofile-testing-junit5 |
Value.as(Class) in Helidon 4 returns OptionalValue<T>, not T. This is the single
most common Helidon 4 compile error in generated code.
Project Setup & Structure
Programming Model: Determine whether the project uses Helidon SE or Helidon MP before generating code. Do not mix the two programming models unless explicitly required.
Java Version: Use Java 21 or later for Helidon 4 applications.
Build Tool: Use Maven (
pom.xml) or Gradle (build.gradle) for dependency management.Dependency Management: Use the Helidon BOM or platform to keep Helidon module versions aligned.
Package Structure: Organize code by feature or domain, such as
com.example.app.orderandcom.example.app.customer, rather than only by technical layer.
Helidon SE
Explicit Composition: Construct services and dependencies explicitly in the application bootstrap layer.
Constructor Injection: Pass required dependencies through constructors and declare dependency fields as
private final.HTTP Services: Group related routes in focused
HttpServiceimplementations.Business Logic: Keep business logic outside route handlers.
Virtual Threads: Prefer straightforward blocking code with Helidon 4 virtual-thread-based request handling. Do not introduce reactive complexity without a clear reason. Helidon 4 is not reactive; do not generate
Single,Multi, orCompletionStagechains.
Helidon MP
Jakarta and MicroProfile: Prefer standard Jakarta EE and Eclipse MicroProfile APIs when available.
Dependency Injection: Use CDI with constructor injection for required dependencies.
Bean Scopes: Use CDI scopes such as
@ApplicationScopedand@RequestScopedintentionally.Normal-Scoped Beans: Add a non-private no-argument constructor to normal-scoped beans that use constructor injection, so the CDI client proxy can be created portably.
Business Logic: Keep Jakarta REST resource classes thin and delegate business operations to service classes.
Portability: Prefer portable Jakarta and MicroProfile APIs over Helidon-specific APIs when portability is important.
Configuration
Externalized Configuration: Store non-secret configuration in
application.yamlorapplication.properties.Helidon SE Configuration: Use Helidon Config and pass configuration values or typed configuration objects to components.
Helidon MP Configuration: Use MicroProfile Config for injected application settings.
Environment Overrides: Use environment variables or deployment-specific configuration sources for environment-dependent values.
Secrets Management: Never hardcode credentials, API keys, tokens, or private certificates.
Web Layer
DTOs: Use dedicated request and response models. Do not expose persistence entities directly through APIs.
Validation: Validate path parameters, query parameters, headers, and request bodies before invoking business logic.
Status Codes: Return appropriate HTTP status codes for successful, invalid, unauthorized, forbidden, missing, and failed requests. On
PUTandDELETE, return 404 when the target does not exist rather than succeeding unconditionally.Error Handling: Use centralized error handling in Helidon SE and Jakarta REST
ExceptionMapperimplementations in Helidon MP.Sensitive Information: Do not expose stack traces, database details, filesystem paths, or internal exception messages to clients.
Helidon SE Example
Use an HttpService to register routes programmatically. Keep request handlers small and delegate business logic to a service.
import io.helidon.http.Status;
import io.helidon.webserver.http.HttpRules;
import io.helidon.webserver.http.HttpService;
import io.helidon.webserver.http.ServerRequest;
import io.helidon.webserver.http.ServerResponse;
public final class CustomerHttpService implements HttpService {
private final CustomerService customerService;
public CustomerHttpService(CustomerService customerService) {
this.customerService = customerService;
}
@Override
public void routing(HttpRules rules) {
rules.get("/{id}", this::findById);
}
private void findById(ServerRequest request, ServerResponse response) {
var id = request.path().pathParameters().get("id");
customerService.findById(id)
.ifPresentOrElse(
response::send,
() -> response.status(Status.NOT_FOUND_404).send()
);
}
}Register the HTTP service when constructing the server:
import io.helidon.webserver.WebServer;
WebServer server = WebServer.builder()
.routing(routing -> routing.register("/customers", customerHttpService))
.build()
.start();Helidon MP Example
Use Jakarta REST annotations for endpoints and CDI for dependency injection.
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@Path("/customers")
@RequestScoped
@Produces(MediaType.APPLICATION_JSON)
public class CustomerResource {
private final CustomerService customerService;
protected CustomerResource() {
this.customerService = null;
}
@Inject
public CustomerResource(CustomerService customerService) {
this.customerService = customerService;
}
@GET
@Path("/{id}")
public Response findById(@PathParam("id") String id) {
return customerService.findById(id)
.map(customer -> Response.ok(customer).build())
.orElseGet(() -> Response.status(Response.Status.NOT_FOUND).build());
}
}Service Layer
Transactions: Define transaction boundaries around complete business operations.
Entity Mapping: Map persistence entities to API models at the service boundary so that service method signatures expose only API models. A service returning
Optional<CustomerEntity>where the caller expectsOptional<Customer>is a common generated-code compile error.Concurrency: Avoid mutable shared state in application-scoped components unless access is properly coordinated.
Helidon SE Example
Helidon SE services are normally plain Java classes with explicitly supplied dependencies.
public final class CustomerService {
private final CustomerRepository customerRepository;
public CustomerService(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
public Optional<Customer> findById(String id) {
if (id == null || id.isBlank()) {
throw new IllegalArgumentException("Customer ID is required");
}
return customerRepository.findById(id);
}
public Customer create(CreateCustomerRequest request) {
if (request.name() == null || request.name().isBlank()) {
throw new IllegalArgumentException("Customer name is required");
}
var customer = new Customer(request.id(), request.name().trim());
customerRepository.save(customer);
return customer;
}
}Construct the dependency graph explicitly:
var repository = new DbCustomerRepository(dbClient);
var service = new CustomerService(repository);
var httpService = new CustomerHttpService(service);Helidon MP Example
Use CDI scopes and constructor injection. Apply transactions at the service layer when a business operation changes persistent state. Map the persistence entity to the API model here, so the service never leaks CustomerEntity to callers.
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
@ApplicationScoped
public class CustomerService {
private final JpaCustomerRepository customerRepository;
protected CustomerService() {
this.customerRepository = null;
}
@Inject
public CustomerService(JpaCustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
public Optional<Customer> findById(String id) {
if (id == null || id.isBlank()) {
throw new IllegalArgumentException("Customer ID is required");
}
return customerRepository.findById(id)
.map(Customer::fromEntity);
}
@Transactional
public Customer create(CreateCustomerRequest request) {
if (request.name() == null || request.name().isBlank()) {
throw new IllegalArgumentException("Customer name is required");
}
var entity = new CustomerEntity(request.id(), request.name().trim());
customerRepository.save(entity);
return Customer.fromEntity(entity);
}
}Data Layer
Database Access: Use Helidon DB Client, Jakarta Persistence, or another persistence mechanism already established by the project.
Parameterized Queries: Always use parameter binding or prepared statements. Never concatenate untrusted input into SQL.
Column Accessors: Read a typed column value with
column("name").getString()(orgetInt(),getLong(), and so on) or withcolumn("name").get(String.class).DbColumn.as(String.class)returns anOptionalValue<String>in Helidon 4, not aString.Nullable Columns: Read nullable columns through
asOptional()or another optional-aware accessor. DirectgetString()and similar accessors throw when the column value is null.Row Mapping: For whole-row mapping,
DbRow.as(Customer.class)returns the mapped instance directly, but requires aDbMapperregistered through aDbMapperProviderservice-loader entry. Prefer explicit column reads for a small number of simple repositories, and introduce aDbMapperwhen the same row shape is mapped in several places.Migrations: Use a database migration tool for schema changes rather than automatic destructive schema updates.
Entity Separation: Do not expose database entities directly as API contracts.
Helidon SE Example
Use Helidon DB Client with parameterized statements. Map database rows into application models inside the repository.
import io.helidon.dbclient.DbClient;
public final class DbCustomerRepository implements CustomerRepository {
private static final String FIND_BY_ID =
"SELECT id, name FROM customers WHERE id = :id";
private static final String INSERT =
"INSERT INTO customers (id, name) VALUES (:id, :name)";
private final DbClient dbClient;
public DbCustomerRepository(DbClient dbClient) {
this.dbClient = dbClient;
}
@Override
public Optional<Customer> findById(String id) {
return dbClient.execute()
.createGet(FIND_BY_ID)
.addParam("id", id)
.execute()
.map(row -> new Customer(
row.column("id").getString(),
row.column("name").getString()
));
}
@Override
public void save(Customer customer) {
dbClient.execute()
.createInsert(INSERT)
.addParam("id", customer.id())
.addParam("name", customer.name())
.execute();
}
}Named statements can also be stored in configuration instead of embedding SQL in Java:
db:
source: "jdbc"
connection:
url: "jdbc:postgresql://localhost:5432/customers"
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
statements:
find-customer-by-id: >
SELECT id, name
FROM customers
WHERE id = :idReference the named statement by name instead of passing SQL text:
return dbClient.execute()
.createNamedGet("find-customer-by-id")
.addParam("id", id)
.execute()
.map(row -> new Customer(
row.column("id").getString(),
row.column("name").getString()
));Helidon MP Example
Use Jakarta Persistence in a CDI-managed repository. Keep transaction boundaries in the service layer. The repository works in entities; the service maps them to API models.
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
@ApplicationScoped
public class JpaCustomerRepository {
@PersistenceContext
private EntityManager entityManager;
public Optional<CustomerEntity> findById(String id) {
return Optional.ofNullable(entityManager.find(CustomerEntity.class, id));
}
public void save(CustomerEntity customer) {
entityManager.persist(customer);
}
}Define the persistence entity separately from the public API model:
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "customers")
public class CustomerEntity {
@Id
private String id;
@Column(nullable = false)
private String name;
protected CustomerEntity() {
}
public CustomerEntity(String id, String name) {
this.id = id;
this.name = name;
}
public String id() {
return id;
}
public String name() {
return name;
}
}The API model stays free of persistence annotations and owns the mapping:
public record Customer(String id, String name) {
public static Customer fromEntity(CustomerEntity entity) {
return new Customer(entity.id(), entity.name());
}
}Observability
Health: Use Helidon Health in SE or MicroProfile Health in MP for liveness and readiness checks.
Metrics: Use Helidon Metrics or MicroProfile Metrics for operational and business measurements.
Tracing: Propagate tracing context across inbound and outbound service calls.
Cardinality: Avoid user IDs, request IDs, email addresses, and raw URLs as metric tags.
Logging
Logging API: Use the logging API and implementation configured by the project.
Sensitive Information: Never log passwords, access tokens, authorization headers, cookies, or complete sensitive request bodies. Do not place secrets or personal information in metrics or trace attributes either.
Testing
Unit Tests: Write unit tests for business services using JUnit 5.
Helidon SE Tests: Use
helidon-webserver-testing-junit5with@ServerTestfor full server tests and@RoutingTestfor routing-only tests. These start the server on a dynamically selected port and inject aHttp1Clientbound to it. Never hardcode a port.Helidon MP Tests: Use
helidon-microprofile-testing-junit5with@HelidonTest, which starts the CDI container and server for the test class. Confirm the artifact coordinates against the Helidon version in use, since this module was renamed across 4.x releases.Testcontainers: Consider Testcontainers for integration tests using real databases, message brokers, or other infrastructure.
Failure Paths: Test validation failures, missing resources, external-service failures, and authorization failures.
Security
Helidon Security: Use Helidon Security or supported Jakarta and MicroProfile security APIs for authentication and authorization.
Authorization: Enforce permissions at a clear application boundary and deny protected operations by default.
JWT and OIDC: Validate token signatures, issuers, audiences, and expiration times.
TLS: Use TLS for production traffic and verify certificates for outbound connections.
CORS: Configure allowed origins explicitly. Do not combine wildcard origins with credentials.
Secrets: Store secrets in protected environment configuration or a dedicated secret-management system.
Outbound Requests: Validate outbound destinations to reduce server-side request forgery risks.
Files
- ATTRIBUTION.mdAdded by AutoGineer859 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md17.7 KiB
ATTRIBUTION.md
Attribution
This folder is an unmodified copy of the Agent Skill "java-helidon", distributed by AutoGineer (https://autogineer.com/agent-skills).
Commit: 797337f33f73e35d08d82f840e21c4b8c4932d88
First committed by: thesurenk (https://github.com/thesurenk)
Publisher: GitHub (https://github.com/github)
Licence: MIT License (https://github.com/github/awesome-copilot/blob/797337f33f73e35d08d82f840e21c4b8c4932d88/LICENSE)
Licence evidence: repository licence LICENSE
Retrieved: 2026-11-07
The skill's own files are unmodified copies. AutoGineer is not affiliated with or endorsed by the author or the publisher.
Files added by AutoGineer
ATTRIBUTION.md: this file, written by AutoGineer
LICENSE: copied from LICENSE
Requirements and compatibility
Written for: GitHub Copilot
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs an API key
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- First committed by
thesurenk
- Publisher
- GitHub (Official)
- Distributed by
- GitHub
- Source
- github/awesome-copilot · skills/java-helidon · commit 797337f
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
- Contributors
- thesurenk
AutoGineer is not affiliated with or endorsed by thesurenk or GitHub.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download java-helidon.zip