This is an automated email from the ASF dual-hosted git repository.
jamesnetherton pushed a commit to branch camel-quarkus-main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus-examples.git
The following commit(s) were added to refs/heads/camel-quarkus-main by this
push:
new 421a8650 Add http-pqc-j25 example using JDK post-quantum TLS without
Bouncy Castle
421a8650 is described below
commit 421a865058a08d7cdb29989abdf6262c0d8b128e
Author: Jiří Ondrušek <[email protected]>
AuthorDate: Tue Nov 10 17:30:37 2026 +0100
Add http-pqc-j25 example using JDK post-quantum TLS without Bouncy Castle
* Add http-pqc-j25 example using JDK post-quantum TLS without Bouncy Castle
Same use case as http-pqc-j21, but the hybrid X25519MLKEM768 key exchange
comes from the JDK's SunJSSE (JDK 25.0.5+ / 27, JEP 527), configured only
through the Quarkus TLS registry (key-exchange-groups,
pqc-enforcement-policy).
Demo certificates are generated during the build by the SmallRye certificate
generator (Maven plugin and @Certificates). Tests are skipped on JDKs older
than 25.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Address review: simplify PQC example
Assert IOException for a missing client certificate (TLS 1.3 may fail after
the handshake).
Call SSLParameters named groups API directly, dropping JDK 17 workarounds.
Reword the response text, note CI skips native mode, remove
quarkus.log.file.enabled.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: James Netherton <[email protected]>
Co-authored-by: jiriOndrusek-agent <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
---
docs/modules/ROOT/attachments/examples.json | 5 +
http-pqc-j25/README.adoc | 153 ++++++++++
http-pqc-j25/eclipse-formatter-config.xml | 276 +++++++++++++++++
http-pqc-j25/pom.xml | 338 +++++++++++++++++++++
.../main/java/org/acme/http/pqc/PqcCamelRoute.java | 45 +++
.../src/main/resources/application.properties | 58 ++++
.../java/org/acme/http/pqc/AbstractPqcTest.java | 132 ++++++++
.../test/java/org/acme/http/pqc/PqcOnlyTest.java | 53 ++++
.../org/acme/http/pqc/PqcSupportedCondition.java | 52 ++++
.../java/org/acme/http/pqc/PqcWithFallbackIT.java | 26 ++
.../org/acme/http/pqc/PqcWithFallbackTest.java | 43 +++
.../org/acme/http/pqc/profiles/PqcOnlyProfile.java | 34 +++
pom.xml | 1 +
13 files changed, 1216 insertions(+)
diff --git a/docs/modules/ROOT/attachments/examples.json
b/docs/modules/ROOT/attachments/examples.json
index 5a8f36bc..92e7aa95 100644
--- a/docs/modules/ROOT/attachments/examples.json
+++ b/docs/modules/ROOT/attachments/examples.json
@@ -59,6 +59,11 @@
"description": "Shows how to implement quantum-resistant TLS using the
BouncyCastle JSSE provider for native PQC support with hybrid cipher suites",
"link":
"https://github.com/apache/camel-quarkus-examples/tree/main/http-pqc"
},
+ {
+ "title": "HTTP with Post-Quantum Cryptography (PQC) (Java 25)",
+ "description": "Shows how to implement quantum-resistant TLS using the
JDK\u0027s own hybrid X25519MLKEM768 key exchange, configured through the
Quarkus TLS registry, without Bouncy Castle",
+ "link":
"https://github.com/apache/camel-quarkus-examples/tree/main/http-pqc-j25"
+ },
{
"title": "HTTP with vanilla JAX-RS or with Camel `platform-http`
component",
"description": "Shows how to create HTTP endpoints using either the
RESTEasy",
diff --git a/http-pqc-j25/README.adoc b/http-pqc-j25/README.adoc
new file mode 100644
index 00000000..16d169d8
--- /dev/null
+++ b/http-pqc-j25/README.adoc
@@ -0,0 +1,153 @@
+= HTTP with Post-Quantum Cryptography (PQC): A Camel Quarkus example (Java 25)
+:cq-example-description: An example that shows how to implement
quantum-resistant TLS using the JDK's own hybrid X25519MLKEM768 key exchange,
configured through the Quarkus TLS registry, without Bouncy Castle
+
+{cq-description}
+
+This example serves a Camel `platform-http` endpoint over mutual TLS 1.3
protected by the hybrid post-quantum key exchange **X25519MLKEM768** (classical
X25519 ECDH combined with ML-KEM-768, NIST FIPS 203).
+
+Unlike the `http-pqc-j21` example, it uses **no Bouncy Castle at all**:
+
+[cols="1,2,2"]
+|===
+| | `http-pqc-j21` | `http-pqc-j25` (this example)
+
+| TLS provider
+| Bouncy Castle JSSE, registered in code
+| JDK SunJSSE
+
+| PQC configuration
+| `jdk.tls.namedGroups` system property
+| Quarkus TLS registry: `quarkus.tls.pqc-enforcement-policy` (JDK default
named groups)
+
+| Certificates
+| Generated at startup with the Bouncy Castle X.509 API
+| Generated at build/test time with the SmallRye certificate generator
+
+| Extra runtime dependencies
+| `camel-quarkus-support-bouncycastle`, `bctls-jdk18on`
+| none
+|===
+
+NOTE: TLS is pure JDK. Bouncy Castle is used only at build and test time, by
the SmallRye certificate generator (Maven plugin and the `test` scoped
`smallrye-certificate-generator-junit5`). It is not on the application
classpath: `mvn dependency:tree -Dscope=runtime` and `target/quarkus-app/lib`
contain no Bouncy Castle jar.
+
+IMPORTANT: Requires a JDK with X25519MLKEM768 in SunJSSE
(https://openjdk.org/jeps/527[JEP 527]): **JDK 25.0.5 (October 2026 update) or
newer, or JDK 27+**. On older JDK 25 builds the application fails at startup
with `PQC enforcement policy ... requires X25519MLKEM768 but the configured SSL
engine does not support it`. Check your JDK (the output must contain
`X25519MLKEM768`):
+
+[source,shell]
+----
+echo 'System.out.println(String.join(",",
javax.net.ssl.SSLContext.getDefault().getSupportedSSLParameters().getNamedGroups()))'
| jshell -q
+----
+
+The tests detect the support at runtime (`PqcSupportedCondition` checks the
JDK's supported TLS named groups) and are skipped on JDKs without
X25519MLKEM768, e.g. JDK 25.0.0 - 25.0.4. The tests also require JDK 25
(`@EnabledForJreRange`). On JDK 21 (CI) the module is compiled with `release`
21 and the tests are skipped.
+
+TIP: Check the
https://camel.apache.org/camel-quarkus/latest/first-steps.html[Camel Quarkus
User guide] for prerequisites and other general information.
+
+== Configuration
+
+The whole PQC setup is in `src/main/resources/application.properties`:
+
+[source,properties]
+----
+quarkus.tls.protocols = TLSv1.3
+quarkus.tls.ssl-engine = jdkssl
+quarkus.tls.pqc-enforcement-policy = client-negotiated
+----
+
+`quarkus.tls.pqc-enforcement-policy` values:
+
+* `client-negotiated` (default here): X25519MLKEM768 is accepted (JDK 25.0.5+
offers it by default), classical clients can still connect. Use it for gradual
migration.
+* `strict`: X25519MLKEM768 is required, only post-quantum hybrid groups are
accepted, classical clients are rejected.
+* `relaxed` (Quarkus default): PQC is neither enforced nor checked at startup.
Do not use it for this purpose.
+
+Both `client-negotiated` and `strict` fail at startup when the JDK does not
support X25519MLKEM768, so the server never silently falls back to
classical-only TLS.
+
+== Mutual TLS and certificates
+
+The server requires a client certificate (`quarkus.http.ssl.client-auth =
required`) and trusts only the demo client certificate
(`quarkus.tls.trust-store`), in both the default and the `strict` setup.
Clients without a certificate are rejected during the TLS handshake.
+
+For running the application by hand, the Maven build (`generate-resources`
phase) creates demo certificates in `target/certs/` with the
`smallrye-certificate-generator-maven-plugin` (password `changeit`, valid 365
days):
+
+* `pqc-keystore.p12`, `pqc-server-truststore.p12` - server identity
(`CN=localhost`, SAN `localhost`, `127.0.0.1`) and trusted client
+* `pqc-client-keystore.p12`, `pqc-client-truststore.p12` - client identity and
trusted server
+* `pqc.crt`, `pqc-client.crt`, `pqc-client.key`, `pqc-client-ca.crt`, ... -
the same in PEM form (e.g. for `curl`)
+
+The tests generate their own certificates in `target/test-certs/`
(`@Certificates` on `AbstractPqcTest`) and use them via the `%test` profile.
+
+The generated keys and the `changeit` passwords are for demo only, do not
reuse them.
+
+The certificates use classical RSA signatures. The post-quantum protection
comes from the key exchange, which defeats "harvest now, decrypt later" attacks
on recorded traffic.
+
+== Start in Development Mode
+
+[source,shell]
+----
+mvn clean compile quarkus:dev
+----
+
+The application starts on a random HTTPS port with mutual TLS. Look for
`Listening on: https://localhost:xxxxx` in the log. Endpoint: `/pqc/secure`.
+
+Any JDK 25.0.5+ client offers X25519MLKEM768 by default. `curl` built with
OpenSSL 3.5+ also negotiates X25519MLKEM768; clients without PQC support fall
back to a classical group thanks to `client-negotiated`:
+
+[source,shell]
+----
+curl --cert target/certs/pqc-client.crt --key target/certs/pqc-client.key \
+ --cacert target/certs/pqc-client-ca.crt \
+ https://localhost:xxxxx/pqc/secure
+----
+
+NOTE: The response text describes the hybrid key exchange; a classical client
connected through the fallback does not get it.
+
+== Package and Run the Application
+
+TIP: Find more details about the JVM mode and Native mode in the Package and
run section of
https://camel.apache.org/camel-quarkus/latest/first-steps.html#_package_and_run_the_application[Camel
Quarkus User guide]
+
+=== JVM Mode
+
+[source,shell]
+----
+mvn clean package
+java -jar target/quarkus-app/quarkus-run.jar
+----
+
+=== Native Mode
+
+IMPORTANT: Native mode requires having GraalVM and other tools installed.
Please check the Prerequisites section of
https://camel.apache.org/camel-quarkus/latest/first-steps.html#_prerequisites[Camel
Quarkus User guide].
+
+The native image must be built by a GraalVM / Mandrel release based on JDK
25.0.5 or newer, otherwise it lacks X25519MLKEM768 and fails at startup. On a
JDK below 25 the `skip-native-below-jdk25` profile turns the native build and
ITs off, even with `-Dnative`. For that reason CI does not run native mode for
this example, so it is verified by hand on a Mandrel/GraalVM 25.0.5+ builder.
+
+[source,shell]
+----
+mvn clean package -Dnative
+./target/*-runner
+----
+
+== Testing
+
+[source,shell]
+----
+mvn clean test
+----
+
+The tests call the server with the JDK TLS stack, restricting the named groups
offered by the client:
+
+* `PqcWithFallbackTest` (`client-negotiated`): hybrid client ✓, classical-only
client (`x25519,secp256r1`) ✓, client without certificate ✗
+* `PqcOnlyTest` (`strict`, via `PqcOnlyProfile`): hybrid client ✓, JDK default
client ✓, classical-only client ✗ (TLS handshake fails), client without
certificate ✗
+
+Native mode runs `PqcWithFallbackIT` only, because a native executable is
built with one configuration:
+
+[source,shell]
+----
+mvn clean verify -Dnative
+----
+
+NOTE: The tests use RestAssured (Apache HttpClient) rather than
`java.net.http.HttpClient`, because the latter ignores
`SSLParameters.setNamedGroups()`.
+
+== Additional Resources
+
+* https://openjdk.org/jeps/527[JEP 527 - Post-Quantum Hybrid Key Exchange for
TLS 1.3]
+* https://quarkus.io/guides/tls-registry-reference[Quarkus TLS registry
reference]
+* https://csrc.nist.gov/pubs/fips/203/final[NIST FIPS 203 - ML-KEM Standard]
+* https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/[IETF Draft -
Hybrid ECDHE-MLKEM Key Agreement for TLS 1.3]
+
+== Feedback
+
+Please report bugs and propose improvements via
https://github.com/apache/camel-quarkus/issues[GitHub issues of Camel Quarkus]
project.
diff --git a/http-pqc-j25/eclipse-formatter-config.xml
b/http-pqc-j25/eclipse-formatter-config.xml
new file mode 100644
index 00000000..2248b2b8
--- /dev/null
+++ b/http-pqc-j25/eclipse-formatter-config.xml
@@ -0,0 +1,276 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<profiles version="8">
+ <profile name="Camel Java Conventions" version="8"
kind="CodeFormatterProfile">
+ <setting
id="org.eclipse.jdt.core.formatter.align_type_members_on_columns"
value="false"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
+ <setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration"
value="16"/>
+ <setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports"
value="1"/>
+ <setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package"
value="1"/>
+ <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field"
value="0"/>
+ <setting
id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration"
value="0"/>
+ <setting
id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
+ <setting
id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
+ <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method"
value="1"/>
+ <setting
id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
+ <setting
id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
+ <setting
id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations"
value="1"/>
+ <setting
id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration"
value="end_of_line"/>
+ <setting
id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration"
value="end_of_line"/>
+ <setting
id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer"
value="end_of_line"/>
+ <setting id="org.eclipse.jdt.core.formatter.brace_position_for_block"
value="end_of_line"/>
+ <setting
id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case"
value="end_of_line"/>
+ <setting
id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration"
value="end_of_line"/>
+ <setting
id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant"
value="end_of_line"/>
+ <setting
id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration"
value="end_of_line"/>
+ <setting
id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration"
value="end_of_line"/>
+ <setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch"
value="end_of_line"/>
+ <setting
id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration"
value="end_of_line"/>
+ <setting
id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions"
value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped"
value="false"/>
+ <setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines"
value="false"/>
+ <setting
id="org.eclipse.jdt.core.formatter.comment.format_block_comments"
value="false"/>
+ <setting id="org.eclipse.jdt.core.formatter.comment.format_comments"
value="true"/>
+ <setting id="org.eclipse.jdt.core.formatter.comment.format_header"
value="false"/>
+ <setting id="org.eclipse.jdt.core.formatter.comment.format_html"
value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments"
value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="false"/>
+ <setting
id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description"
value="false"/>
+ <setting
id="org.eclipse.jdt.core.formatter.comment.indent_return_description"
value="false"/>
+ <setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags"
value="false"/>
+ <setting
id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter"
value="do not insert"/>
+ <setting id="org.eclipse.jdt.core.formatter.comment.line_length"
value="120"/>
+ <setting id="org.eclipse.jdt.core.formatter.compact_else_if"
value="true"/>
+ <setting id="org.eclipse.jdt.core.formatter.continuation_indentation"
value="2"/>
+ <setting
id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer"
value="2"/>
+ <setting
id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line"
value="false"/>
+ <setting
id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header"
value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header"
value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header"
value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases"
value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block"
value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body"
value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases"
value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch"
value="false"/>
+ <setting id="org.eclipse.jdt.core.formatter.indentation.size"
value="8"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do
not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do
not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not
insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not
insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation"
value="do not insert"/>
+ <setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments"
value="false"/>
+ <setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines"
value="false"/>
+ <setting
id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line"
value="false"/>
+ <setting
id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line"
value="false"/>
+ <setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line"
value="false"/>
+ <setting
id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line"
value="false"/>
+ <setting id="org.eclipse.jdt.core.formatter.lineSplit" value="128"/>
+ <setting
id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body"
value="0"/>
+ <setting
id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve"
value="1"/>
+ <setting
id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line"
value="true"/>
+ <setting id="org.eclipse.jdt.core.formatter.tabulation.char"
value="space"/>
+ <setting id="org.eclipse.jdt.core.formatter.tabulation.size"
value="4"/>
+ <setting
id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations"
value="false"/>
+ <setting id="org.eclipse.jdt.core.formatter.use_on_off_tags"
value="true"/>
+ <setting id="org.eclipse.jdt.core.formatter.disabling_tag"
value="CHECKSTYLE:OFF"/>
+ <setting id="org.eclipse.jdt.core.formatter.enabling_tag"
value="CHECKSTYLE:ON"/>
+ </profile>
+</profiles>
diff --git a/http-pqc-j25/pom.xml b/http-pqc-j25/pom.xml
new file mode 100644
index 00000000..7e906388
--- /dev/null
+++ b/http-pqc-j25/pom.xml
@@ -0,0 +1,338 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>camel-quarkus-examples-http-pqc-j25</artifactId>
+ <groupId>org.apache.camel.quarkus.examples</groupId>
+ <version>4.0.0-SNAPSHOT</version>
+
+ <name>Camel Quarkus :: Examples :: HTTP PQC Java 25</name>
+ <description>Camel Quarkus Example :: HTTP with Post-Quantum Cryptography
on Java 25 using X25519MLKEM768 without Bouncy Castle</description>
+
+ <properties>
+ <quarkus.platform.version>4.0.0.Beta3</quarkus.platform.version>
+
<camel-quarkus.platform.version>4.0.0-SNAPSHOT</camel-quarkus.platform.version>
+
+ <quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
+
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
+
<camel-quarkus.platform.group-id>org.apache.camel.quarkus</camel-quarkus.platform.group-id>
+
<camel-quarkus.platform.artifact-id>camel-quarkus-bom</camel-quarkus.platform.artifact-id>
+
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+ <!-- Builds on JDK 21+ (CI runs with JDK 21 and 25); the tests are
skipped at runtime below JDK 25 -->
+ <maven.compiler.release>21</maven.compiler.release>
+
+ <formatter-maven-plugin.version>2.29.0</formatter-maven-plugin.version>
+ <impsort-maven-plugin.version>1.13.0</impsort-maven-plugin.version>
+ <license-maven-plugin.version>5.1.2</license-maven-plugin.version>
+ <maven-compiler-plugin.version>3.16.0</maven-compiler-plugin.version>
+ <maven-jar-plugin.version>3.5.1</maven-jar-plugin.version>
+ <maven-surefire-plugin.version>3.5.6</maven-surefire-plugin.version>
+ <!-- Keep aligned with smallrye-certificate-generator managed by the
Quarkus BOM -->
+
<smallrye-certificate-generator.version>1.1.3</smallrye-certificate-generator.version>
+
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <!-- Import BOM -->
+ <dependency>
+ <groupId>${quarkus.platform.group-id}</groupId>
+ <artifactId>${quarkus.platform.artifact-id}</artifactId>
+ <version>${quarkus.platform.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>${camel-quarkus.platform.group-id}</groupId>
+ <artifactId>${camel-quarkus.platform.artifact-id}</artifactId>
+ <version>${camel-quarkus.platform.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-platform-http</artifactId>
+ </dependency>
+
+ <!-- Test -->
+ <dependency>
+ <groupId>io.quarkus</groupId>
+ <artifactId>quarkus-junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.rest-assured</groupId>
+ <artifactId>rest-assured</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.smallrye.certs</groupId>
+ <artifactId>smallrye-certificate-generator-junit5</artifactId>
+ <version>${smallrye-certificate-generator.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <pluginManagement>
+ <plugins>
+
+ <plugin>
+ <groupId>net.revelc.code.formatter</groupId>
+ <artifactId>formatter-maven-plugin</artifactId>
+ <version>${formatter-maven-plugin.version}</version>
+ <configuration>
+
<configFile>${maven.multiModuleProjectDirectory}/eclipse-formatter-config.xml</configFile>
+ <lineEnding>LF</lineEnding>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>net.revelc.code</groupId>
+ <artifactId>impsort-maven-plugin</artifactId>
+ <version>${impsort-maven-plugin.version}</version>
+ <configuration>
+ <groups>java.,javax.,org.w3c.,org.xml.,junit.</groups>
+ <removeUnused>true</removeUnused>
+ <staticAfter>true</staticAfter>
+
<staticGroups>java.,javax.,org.w3c.,org.xml.,junit.</staticGroups>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>${maven-compiler-plugin.version}</version>
+ <configuration>
+ <showDeprecation>true</showDeprecation>
+ <showWarnings>true</showWarnings>
+ <compilerArgs>
+ <arg>-Xlint:unchecked</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>${maven-surefire-plugin.version}</version>
+ <configuration>
+ <failIfNoTests>false</failIfNoTests>
+ <systemPropertyVariables>
+
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>${quarkus.platform.group-id}</groupId>
+ <artifactId>quarkus-maven-plugin</artifactId>
+ <version>${quarkus.platform.version}</version>
+ <extensions>true</extensions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <version>${maven-surefire-plugin.version}</version>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>${maven-jar-plugin.version}</version>
+ </plugin>
+
+ <plugin>
+ <groupId>com.mycila</groupId>
+ <artifactId>license-maven-plugin</artifactId>
+ <version>${license-maven-plugin.version}</version>
+ <configuration>
+ <failIfUnknown>true</failIfUnknown>
+
<header>${maven.multiModuleProjectDirectory}/header.txt</header>
+ <excludes>
+ <exclude>**/*.adoc</exclude>
+ <exclude>**/*.txt</exclude>
+ <exclude>**/LICENSE.txt</exclude>
+ <exclude>**/LICENSE</exclude>
+ <exclude>**/NOTICE.txt</exclude>
+ <exclude>**/NOTICE</exclude>
+ <exclude>**/README</exclude>
+ <exclude>**/pom.xml.versionsBackup</exclude>
+ <exclude>**/quarkus.log*</exclude>
+ <exclude>**/*.p12</exclude>
+ </excludes>
+ <mapping>
+ <java>SLASHSTAR_STYLE</java>
+ <properties>CAMEL_PROPERTIES_STYLE</properties>
+ <kt>SLASHSTAR_STYLE</kt>
+ </mapping>
+ <headerDefinitions>
+
<headerDefinition>${maven.multiModuleProjectDirectory}/license-properties-headerdefinition.xml</headerDefinition>
+ </headerDefinitions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+
+ <plugins>
+
+ <!-- Demo certificates for running the application by hand (the
tests generate their own).
+ Bouncy Castle is used by this plugin only, it is not on the
application classpath. -->
+ <plugin>
+ <groupId>io.smallrye.certs</groupId>
+
<artifactId>smallrye-certificate-generator-maven-plugin</artifactId>
+ <version>${smallrye-certificate-generator.version}</version>
+ <executions>
+ <execution>
+ <id>generate-certificates</id>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>generate</goal>
+ </goals>
+ <configuration>
+
<outputDirectory>${project.build.directory}/certs</outputDirectory>
+ <certificates>
+ <certificate>
+ <name>pqc</name>
+ <formats>
+ <format>PKCS12</format>
+ <format>PEM</format>
+ </formats>
+ <password>changeit</password>
+ <duration>365</duration>
+ <client>true</client>
+ <subjectAlternativeNames>
+
<subjectAlternativeName>DNS:localhost</subjectAlternativeName>
+
<subjectAlternativeName>IP:127.0.0.1</subjectAlternativeName>
+ </subjectAlternativeNames>
+ </certificate>
+ </certificates>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>${quarkus.platform.group-id}</groupId>
+ <artifactId>quarkus-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>build</id>
+ <goals>
+ <goal>build</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>net.revelc.code.formatter</groupId>
+ <artifactId>formatter-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>format</id>
+ <goals>
+ <goal>format</goal>
+ </goals>
+ <phase>process-sources</phase>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>net.revelc.code</groupId>
+ <artifactId>impsort-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>sort-imports</id>
+ <goals>
+ <goal>sort</goal>
+ </goals>
+ <phase>process-sources</phase>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>com.mycila</groupId>
+ <artifactId>license-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>license-format</id>
+ <goals>
+ <goal>format</goal>
+ </goals>
+ <phase>process-sources</phase>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>native</id>
+ <activation>
+ <property>
+ <name>native</name>
+ </property>
+ </activation>
+ <properties>
+ <quarkus.native.enabled>true</quarkus.native.enabled>
+ </properties>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <!-- This example needs JDK 25.0.5+, but CI runs the native build on
JDK 21 -->
+ <profile>
+ <id>skip-native-below-jdk25</id>
+ <activation>
+ <jdk>(,25)</jdk>
+ </activation>
+ <properties>
+ <quarkus.native.enabled>false</quarkus.native.enabled>
+ <skipITs>true</skipITs>
+ </properties>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/http-pqc-j25/src/main/java/org/acme/http/pqc/PqcCamelRoute.java
b/http-pqc-j25/src/main/java/org/acme/http/pqc/PqcCamelRoute.java
new file mode 100644
index 00000000..3b8fc8c9
--- /dev/null
+++ b/http-pqc-j25/src/main/java/org/acme/http/pqc/PqcCamelRoute.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme.http.pqc;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import org.apache.camel.builder.endpoint.EndpointRouteBuilder;
+
+/**
+ * Simple route demonstrating Post-Quantum Cryptography (PQC) with TLS 1.3.
+ */
+@ApplicationScoped
+public class PqcCamelRoute extends EndpointRouteBuilder {
+
+ private static final String SECURE_RESPONSE = "Secure data delivered over
mutual TLS.\n\n" +
+ "Connection details:\n" +
+ "- TLS key exchange: negotiated according to the server's PQC
policy\n" +
+ "- Provider: SunJSSE (JDK 25, no Bouncy Castle)\n\n" +
+ "When X25519MLKEM768 is negotiated, it protects the session key
against\n" +
+ "harvest-now-decrypt-later attacks. Authentication is classical
(RSA).";
+
+ @Override
+ public void configure() throws Exception {
+ // API endpoint for secure data delivery over PQC-enabled TLS
+ // Client certificates are validated at TLS layer
(quarkus.http.ssl.client-auth=required)
+ from(platformHttp("/pqc/secure"))
+ .routeId("pqc-secure")
+ .log("Serving secure data via PQC-enabled TLS connection")
+ .setBody(constant(SECURE_RESPONSE))
+ .setHeader("Content-Type", constant("text/plain;
charset=utf-8"));
+ }
+}
diff --git a/http-pqc-j25/src/main/resources/application.properties
b/http-pqc-j25/src/main/resources/application.properties
new file mode 100644
index 00000000..fc939cef
--- /dev/null
+++ b/http-pqc-j25/src/main/resources/application.properties
@@ -0,0 +1,58 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements. See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License. You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+#
+# Quarkus
+#
+quarkus.banner.enabled = false
+
+# HTTPS only, on a random port (prevents port conflicts in tests)
+quarkus.http.ssl-port = 0
+quarkus.http.insecure-requests = disabled
+
+# Require client certificates (mutual TLS)
+quarkus.http.ssl.client-auth = required
+
+#
+# TLS registry (default bucket, used by the HTTP server)
+# Server key store, and trust store with the client certificate.
+# Generated by smallrye-certificate-generator-maven-plugin during the Maven
build, see pom.xml
+#
+quarkus.tls.key-store.p12.path = target/certs/pqc-keystore.p12
+# Demo only: generated keys and "changeit" passwords, do not reuse
+quarkus.tls.key-store.p12.password = changeit
+quarkus.tls.trust-store.p12.path = target/certs/pqc-server-truststore.p12
+quarkus.tls.trust-store.p12.password = changeit
+# The tests generate their own certificates (@Certificates in AbstractPqcTest)
+%test.quarkus.tls.key-store.p12.path = target/test-certs/pqc-keystore.p12
+%test.quarkus.tls.trust-store.p12.path =
target/test-certs/pqc-server-truststore.p12
+# The packaged application (PqcWithFallbackIT) uses the test certificates as
well
+quarkus.test.integration-test-profile = test
+
+# TLS 1.3 is required for hybrid key exchange
+quarkus.tls.protocols = TLSv1.3
+
+# Use the JDK's own TLS stack (SunJSSE), no Bouncy Castle or OpenSSL
+quarkus.tls.ssl-engine = jdkssl
+
+# Hybrid post-quantum key exchange (X25519 + ML-KEM-768), with classical
fallback.
+# Named groups are the JDK defaults (JDK 25.0.5+ includes X25519MLKEM768).
+# 'client-negotiated' accepts X25519MLKEM768 but still accepts classical-only
clients.
+# 'strict' accepts only post-quantum groups (see PqcOnlyProfile in the tests).
+# Both fail at startup if the JDK does not support X25519MLKEM768.
+# 'relaxed' (Quarkus default) enforces nothing and does no
+# startup check, so PQC silently depends on the JDK. Do not use it here.
+quarkus.tls.pqc-enforcement-policy = client-negotiated
diff --git a/http-pqc-j25/src/test/java/org/acme/http/pqc/AbstractPqcTest.java
b/http-pqc-j25/src/test/java/org/acme/http/pqc/AbstractPqcTest.java
new file mode 100644
index 00000000..44a3521c
--- /dev/null
+++ b/http-pqc-j25/src/test/java/org/acme/http/pqc/AbstractPqcTest.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme.http.pqc;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyStore;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManagerFactory;
+
+import io.restassured.RestAssured;
+import io.restassured.config.RestAssuredConfig;
+import io.restassured.config.SSLConfig;
+import io.restassured.specification.RequestSpecification;
+import io.smallrye.certs.Format;
+import io.smallrye.certs.junit5.Certificate;
+import io.smallrye.certs.junit5.Certificates;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.junit.jupiter.api.condition.EnabledForJreRange;
+import org.junit.jupiter.api.condition.JRE;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Calls the server with the JDK TLS stack (SunJSSE, no Bouncy Castle),
restricting the TLS named groups the client
+ * offers. The certificates for mutual TLS are generated before the tests
(Bouncy Castle is used for that in test
+ * scope only).
+ * <p>
+ * Runs on JDK 25+ only, and only when it supports X25519MLKEM768 (JDK
25.0.5+, see {@link PqcSupportedCondition}).
+ */
+@EnabledForJreRange(min = JRE.JAVA_25)
+@ExtendWith(PqcSupportedCondition.class)
+@Certificates(baseDir = "target/test-certs", certificates = @Certificate(name
= "pqc", password = "changeit", formats = Format.PKCS12, client = true,
subjectAlternativeNames = {
+ "DNS:localhost", "IP:127.0.0.1" }))
+abstract class AbstractPqcTest {
+
+ /** Hybrid post-quantum key exchange only. */
+ static final String[] HYBRID_GROUPS = { "X25519MLKEM768" };
+ /** Classical key exchange only, as offered by a client without
post-quantum support. */
+ static final String[] CLASSICAL_GROUPS = { "x25519", "secp256r1" };
+
+ private static final char[] PASSWORD = "changeit".toCharArray();
+
+ /**
+ * @param expectSuccess whether the secure endpoint must answer, otherwise
the TLS handshake must fail
+ * @param namedGroups the TLS named groups offered by the client, or
{@code null} for the JDK defaults
+ */
+ void assertRequest(boolean expectSuccess, String[] namedGroups) throws
Exception {
+ if (expectSuccess) {
+ request(namedGroups, true)
+ .get("/pqc/secure")
+ .then()
+ .statusCode(200)
+ .body(containsString("Secure data delivered"));
+ } else {
+ assertThrows(SSLException.class, () -> request(namedGroups,
true).get("/pqc/secure"));
+ }
+ }
+
+ /** Mutual TLS: a client without a certificate is rejected, even when it
offers the hybrid group. */
+ void assertRejectedWithoutClientCertificate() {
+ // TLS 1.3 reports the missing certificate after the handshake, so the
failure may be a plain IOException
+ // (e.g. SocketException) instead of an SSLException
+ assertThrows(IOException.class, () -> request(HYBRID_GROUPS,
false).get("/pqc/secure"));
+ }
+
+ /**
+ * @param namedGroups the TLS named groups offered by the client, or
{@code null} for the JDK defaults
+ * @param clientCertificate whether the client presents its certificate
+ */
+ private static RequestSpecification request(String[] namedGroups, boolean
clientCertificate) throws Exception {
+ SSLSocketFactory socketFactory = new
SSLSocketFactory(createSslContext(clientCertificate),
+ SSLSocketFactory.STRICT_HOSTNAME_VERIFIER) {
+ @Override
+ protected void prepareSocket(SSLSocket socket) {
+ SSLParameters parameters = socket.getSSLParameters();
+ parameters.setProtocols(new String[] { "TLSv1.3" });
+ if (namedGroups != null) {
+ parameters.setNamedGroups(namedGroups);
+ }
+ socket.setSSLParameters(parameters);
+ }
+ };
+ return given()
+
.config(RestAssuredConfig.config().sslConfig(SSLConfig.sslConfig().sslSocketFactory(socketFactory)))
+ .baseUri("https://localhost:" + RestAssured.port);
+ }
+
+ private static SSLContext createSslContext(boolean clientCertificate)
throws Exception {
+ KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ kmf.init(loadKeyStore("target/test-certs/pqc-client-keystore.p12"),
PASSWORD);
+
+ TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init(loadKeyStore("target/test-certs/pqc-client-truststore.p12"));
+
+ SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
+ sslContext.init(clientCertificate ? kmf.getKeyManagers() : null,
tmf.getTrustManagers(), null);
+ return sslContext;
+ }
+
+ private static KeyStore loadKeyStore(String path) throws Exception {
+ KeyStore keyStore = KeyStore.getInstance("PKCS12");
+ try (InputStream in = Files.newInputStream(Path.of(path))) {
+ keyStore.load(in, PASSWORD);
+ }
+ return keyStore;
+ }
+}
diff --git a/http-pqc-j25/src/test/java/org/acme/http/pqc/PqcOnlyTest.java
b/http-pqc-j25/src/test/java/org/acme/http/pqc/PqcOnlyTest.java
new file mode 100644
index 00000000..91a095b7
--- /dev/null
+++ b/http-pqc-j25/src/test/java/org/acme/http/pqc/PqcOnlyTest.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme.http.pqc;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.TestProfile;
+import org.acme.http.pqc.profiles.PqcOnlyProfile;
+import org.junit.jupiter.api.Test;
+
+/**
+ * PQC-only server ({@code strict} policy): only clients offering
X25519MLKEM768 can connect.
+ *
+ * No native *IT counterpart: a native executable is built with one
configuration only (the default, with fallback).
+ */
+@QuarkusTest
+@TestProfile(PqcOnlyProfile.class)
+class PqcOnlyTest extends AbstractPqcTest {
+
+ @Test
+ void hybridClientIsAccepted() throws Exception {
+ assertRequest(true, HYBRID_GROUPS);
+ }
+
+ @Test
+ void jdkDefaultClientIsAccepted() throws Exception {
+ // JDK 25.0.5+ offers X25519MLKEM768 by default
+ assertRequest(true, null);
+ }
+
+ @Test
+ void classicalClientIsRejected() throws Exception {
+ assertRequest(false, CLASSICAL_GROUPS);
+ }
+
+ @Test
+ void clientWithoutCertificateIsRejected() {
+ assertRejectedWithoutClientCertificate();
+ }
+}
diff --git
a/http-pqc-j25/src/test/java/org/acme/http/pqc/PqcSupportedCondition.java
b/http-pqc-j25/src/test/java/org/acme/http/pqc/PqcSupportedCondition.java
new file mode 100644
index 00000000..3a9edeb2
--- /dev/null
+++ b/http-pqc-j25/src/test/java/org/acme/http/pqc/PqcSupportedCondition.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme.http.pqc;
+
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+import javax.net.ssl.SSLContext;
+
+import org.junit.jupiter.api.extension.ConditionEvaluationResult;
+import org.junit.jupiter.api.extension.ExecutionCondition;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+/**
+ * Skips the tests when the running JDK's TLS stack (SunJSSE) does not support
X25519MLKEM768 (JEP 527), e.g. JDK 25
+ * before 25.0.5. The application would fail at startup there.
+ */
+class PqcSupportedCondition implements ExecutionCondition {
+
+ private static final String HYBRID_GROUP = "X25519MLKEM768";
+
+ @Override
+ public ConditionEvaluationResult
evaluateExecutionCondition(ExtensionContext context) {
+ return isSupported()
+ ? ConditionEvaluationResult.enabled(HYBRID_GROUP + " is
supported by the JDK")
+ : ConditionEvaluationResult.disabled(HYBRID_GROUP + " is not
supported by JDK "
+ + Runtime.version() + ", JDK 25.0.5+ is required");
+ }
+
+ static boolean isSupported() {
+ try {
+ String[] groups =
SSLContext.getDefault().getDefaultSSLParameters().getNamedGroups();
+ return groups != null &&
Arrays.asList(groups).contains(HYBRID_GROUP);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+}
diff --git
a/http-pqc-j25/src/test/java/org/acme/http/pqc/PqcWithFallbackIT.java
b/http-pqc-j25/src/test/java/org/acme/http/pqc/PqcWithFallbackIT.java
new file mode 100644
index 00000000..71614d3a
--- /dev/null
+++ b/http-pqc-j25/src/test/java/org/acme/http/pqc/PqcWithFallbackIT.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme.http.pqc;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+/**
+ * Runs PqcWithFallbackTest against the packaged application (native mode:
{@code mvn verify -Dnative}).
+ */
+@QuarkusIntegrationTest
+class PqcWithFallbackIT extends PqcWithFallbackTest {
+}
diff --git
a/http-pqc-j25/src/test/java/org/acme/http/pqc/PqcWithFallbackTest.java
b/http-pqc-j25/src/test/java/org/acme/http/pqc/PqcWithFallbackTest.java
new file mode 100644
index 00000000..c2056432
--- /dev/null
+++ b/http-pqc-j25/src/test/java/org/acme/http/pqc/PqcWithFallbackTest.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme.http.pqc;
+
+import io.quarkus.test.junit.QuarkusTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Default configuration ({@code client-negotiated} policy): hybrid key
exchange is accepted, classical clients can
+ * still connect.
+ */
+@QuarkusTest
+class PqcWithFallbackTest extends AbstractPqcTest {
+
+ @Test
+ void hybridClientIsAccepted() throws Exception {
+ assertRequest(true, HYBRID_GROUPS);
+ }
+
+ @Test
+ void classicalClientIsAccepted() throws Exception {
+ assertRequest(true, CLASSICAL_GROUPS);
+ }
+
+ @Test
+ void clientWithoutCertificateIsRejected() {
+ assertRejectedWithoutClientCertificate();
+ }
+}
diff --git
a/http-pqc-j25/src/test/java/org/acme/http/pqc/profiles/PqcOnlyProfile.java
b/http-pqc-j25/src/test/java/org/acme/http/pqc/profiles/PqcOnlyProfile.java
new file mode 100644
index 00000000..0c8e0055
--- /dev/null
+++ b/http-pqc-j25/src/test/java/org/acme/http/pqc/profiles/PqcOnlyProfile.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme.http.pqc.profiles;
+
+import java.util.Map;
+
+import io.quarkus.test.junit.QuarkusTestProfile;
+
+/**
+ * PQC-only server: the policy is switched to {@code strict}. Vert.x then
replaces the configured key exchange groups
+ * with the hybrid PQC groups (X25519MLKEM768, SecP256r1MLKEM768,
SecP384r1MLKEM1024), so classical-only clients are
+ * rejected.
+ */
+public class PqcOnlyProfile implements QuarkusTestProfile {
+
+ @Override
+ public Map<String, String> getConfigOverrides() {
+ return Map.of("quarkus.tls.pqc-enforcement-policy", "strict");
+ }
+}
diff --git a/pom.xml b/pom.xml
index bb992294..f3c48eea 100644
--- a/pom.xml
+++ b/pom.xml
@@ -49,6 +49,7 @@
<module>file-split-log-xml</module>
<module>http-log</module>
<module>http-pqc</module>
+ <module>http-pqc-j25</module>
<module>jdbc-datasource</module>
<module>jms-jpa</module>
<module>jpa-idempotent-repository</module>