Skip to content

Quarkus CAPTCHA Integration

This recipe shows how to integrate TrustCaptcha into a Quarkus application. The frontend setup is the same as for any other JVM application — this page focuses on the server-side validation.

The setup section gets you to a working integration in three small steps using a JAX-RS resource directly. Below it, an optional refactor section shows the more reusable Quarkus-idiomatic approach (a Bean Validation annotation backed by a CDI-managed validator).

You should have already completed the following steps before you wire TrustCaptcha into your Quarkus application.

  1. Read Get-Started: Get a quick overview of the concepts behind TrustCaptcha and the integration process in get started.

  2. Existing CAPTCHA: If you don’t have a CAPTCHA yet, sign in or create a new user account. Then create a new CAPTCHA.


First, add the TrustCaptcha script to your page (see the JavaScript Guide for version pinning and self-hosting options).

Then place the <trustcaptcha-component> element inside your form. The widget appends a hidden tc-verification-token field on submit, which your Quarkus resource receives like any other form input.

<script type="module" src="https://cdn.trustcomponent.com/trustcaptcha/3.0.x/trustcaptcha.esm.min.js"></script>
<form method="post" action="/contact" enctype="application/x-www-form-urlencoded">
<label>Email</label>
<input type="email" name="email" required>
<trustcaptcha-component sitekey="<your_site_key>"></trustcaptcha-component>
<button type="submit">Send</button>
</form>

See the Widget Overview for the full property reference.

pom.xml
<dependency>
<groupId>com.trustcomponent</groupId>
<artifactId>trustcaptcha</artifactId>
<version>3.0.0</version>
</dependency>

You’ll also need quarkus-rest (or quarkus-resteasy-reactive) and quarkus-hibernate-validator if not already present.

src/main/java/com/example/contact/ContactResource.java
package com.example.contact;
import com.trustcomponent.trustcaptcha.TrustCaptcha;
import com.trustcomponent.trustcaptcha.exception.CaptchaFailureException;
import com.trustcomponent.trustcaptcha.model.VerificationResult;
import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;
@Path("/contact")
public class ContactResource {
@POST
public Response submit(@FormParam("tc-verification-token") String token) {
// In production, load from configuration: @ConfigProperty(name = "trustcaptcha.api-key") String apiKey;
String apiKey = "<your_api_key>";
VerificationResult result;
try {
result = TrustCaptcha.getVerificationResult(apiKey, token);
} catch (CaptchaFailureException e) {
return Response.status(Response.Status.BAD_REQUEST).entity("CAPTCHA verification failed.").build();
}
if (!result.isVerificationPassed() || result.getScore() > 0.5) {
return Response.status(Response.Status.BAD_REQUEST).entity("CAPTCHA verification failed.").build();
}
// CAPTCHA passed — request data is safe to use.
// ... your business logic ...
return Response.ok("Thanks!").build();
}
}

That’s it — the form is now protected. For real deployments, move the API key out of the source code (see the comment) and consider explicit failover handling — see Failover Behavior for the reasoning and a code template.


Refactor: extract to a Bean Validation annotation

Section titled “Refactor: extract to a Bean Validation annotation”

If you protect more than one endpoint, the most idiomatic Quarkus approach is a custom Bean Validation annotation. The verification call then runs automatically whenever a parameter is annotated with @Valid.

src/main/resources/application.properties
trustcaptcha.api-key=${TRUSTCAPTCHA_API_KEY}
src/main/java/com/example/captcha/TrustCaptchaToken.java
package com.example.captcha;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = TrustCaptchaTokenValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface TrustCaptchaToken {
String message() default "CAPTCHA verification failed.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
src/main/java/com/example/captcha/TrustCaptchaTokenValidator.java
package com.example.captcha;
import com.trustcomponent.trustcaptcha.TrustCaptcha;
import com.trustcomponent.trustcaptcha.exception.CaptchaFailureException;
import com.trustcomponent.trustcaptcha.model.VerificationResult;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.eclipse.microprofile.config.inject.ConfigProperty;
@ApplicationScoped
public class TrustCaptchaTokenValidator implements ConstraintValidator<TrustCaptchaToken, String> {
@ConfigProperty(name = "trustcaptcha.api-key")
String apiKey;
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null || value.isBlank()) return false;
try {
VerificationResult result = TrustCaptcha.getVerificationResult(apiKey, value);
return result.isVerificationPassed() && result.getScore() <= 0.5;
} catch (CaptchaFailureException e) {
return false;
}
}
}
src/main/java/com/example/contact/ContactForm.java
package com.example.contact;
import com.example.captcha.TrustCaptchaToken;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.ws.rs.FormParam;
public class ContactForm {
@NotBlank @Email
@FormParam("email")
public String email;
@TrustCaptchaToken
@FormParam("tc-verification-token")
public String tcVerificationToken;
}
src/main/java/com/example/contact/ContactResource.java
import jakarta.ws.rs.BeanParam;
import jakarta.validation.Valid;
@Path("/contact")
public class ContactResource {
@POST
public Response submit(@Valid @BeanParam ContactForm form) {
// CAPTCHA already validated by @TrustCaptchaToken.
return Response.ok("Thanks!").build();
}
}

@FormParam("tc-verification-token") keeps the wire format unchanged (the widget posts the dashed name) while the Java field uses a Java-friendly name. @Valid @BeanParam triggers all Bean Validation constraints, including @TrustCaptchaToken.


Quarkus REST. This recipe targets quarkus-rest (RESTEasy Reactive). The same code works on the older quarkus-resteasy-classic — only the dependency name changes.

Reactive endpoints. The JVM SDK is blocking. If your resource method returns Uni<...> or Multi<...>, perform the verification on a worker thread using Uni.createFrom().item(() -> TrustCaptcha.getVerificationResult(...)).runSubscriptionOn(Infrastructure.getDefaultWorkerPool()).

Native image. For Quarkus native builds, Jackson reflection on VerificationResult is required. If you build with quarkus-jackson, this works out of the box. With Quarkus’ native image generation, no extra reflection config is needed for the SDK as of v3.

Singleton SDK instance. For configured usage (custom timeouts, proxy, custom API host), produce a single TrustCaptcha @ApplicationScoped bean via a @Produces method built with TrustCaptcha.builder(apiKey)... and @Inject it into the validator instead of using the static shortcut. See the JVM Guide for the builder API.


Once you have wired TrustCaptcha into your Quarkus application, you can use TrustCaptcha to its full extent. However, we still recommend the following additional technical and organizational measures:

  • Security rules: You can find many security settings for your CAPTCHA in the CAPTCHA settings. These include, for example, authorized websites, CAPTCHA bypass for specific IP addresses, bypass keys, IP based blocking, geoblocking, individual difficulty and duration of the CAPTCHA, and much more. Learn more about the security rules.

  • Privacy & GDPR compliance: Include a passage in your privacy policy that refers to the use of TrustCaptcha. We also recommend that you enter into a data processing agreement with us to stay GDPR-compliant. Learn more about data protection.

  • Accessibility & UX: Customize TrustCaptcha to your website so that your website is as accessible as possible and offers the best possible user experience. More about accessibility.

  • Failover behavior: Decide how your backend should behave when our service is temporarily unreachable. This is particularly important for high-availability flows where blocking real users during an outage is worse than letting through a small amount of unverified traffic. Learn more about failover behavior.

  • Testing: If you use automated testing, make sure that the CAPTCHA does not block it. Learn more about testing.