Mert Karaman
Mert Karaman

Reputation: 11

How can I use a common Java 11 library with validation annotations in a Java 17 Spring Boot service?

I have three projects:

1- common-lib, Written with Java 11. It doesn't use Spring, but contains shared classes, error codes, models, DTOs, etc., including custom validation annotations (see below).

2- x service, Written with Java 11, using Spring 2.6.

3- y service, Written with Java 17, using Spring 3.2.

Problem: My Java 11 services (x service) can use common-lib easily, including the validation annotations. However, my Java 17 service (y service) can use the models, error codes, and other common classes from common-lib, but cannot use the validation annotations.

The issue appears to be related to the transition from javax.validation to jakarta.validation and other potential bytecode issues.

@Target({TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = IsValidSms.SmsValidator.class)
public @interface IsValidSms {

    String message() default "INVALID_REQUEST_BODY";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    class SmsValidator implements ConstraintValidator<IsValidSms, SmsRequest> {

        public boolean isValid(SmsRequest smsRequest, ConstraintValidatorContext constraintContext) {
            final String smsType = smsRequest.getSmsType();
            if (Objects.isNull(smsType) || smsType.isBlank()) {
                return Objects.nonNull(smsRequest.getContent());
            }
            return isValidPlaceholders(smsRequest.getPlaceholders());
        }

        private boolean isValidPlaceholders(Map<String, String> placeholders) {
            if (Objects.isNull(placeholders) || placeholders.isEmpty()) {
                return true;
            }
            return placeholders.keySet().stream().allMatch(key -> key.startsWith("{{") && key.endsWith("}}"));
        }
    }
}

Context: common-lib is in Java 11 and uses javax.validation annotations. y service is in Java 17 and uses Spring 3.2, which is built on jakarta.validation. The Java 17 service can’t resolve the javax.validation annotations from the common-lib due to changes in the bytecode and the switch from javax to jakarta.

Question: How can I resolve this issue and make the common-lib (Java 11, javax.validation) work with the Java 17 service (which uses Spring 3.2 and jakarta.validation)?

What I've tried: Including jakarta.validation dependencies in the y service, but that doesn't resolve the issue. Adding javax.validation dependency manually to the y service. I'm looking for a solution that allows me to use the validation annotations in the y service without changing the common-lib project.

common-lib ->

dependencies {
    testImplementation 'org.mockito:mockito-core:3.5.9'
    testImplementation 'org.mockito:mockito-junit-jupiter:3.5.9'
    compileOnly 'org.projectlombok:lombok'
    implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.11.3'
    implementation group: 'org.hibernate.validator', name: 'hibernate-validator', version: '6.1.6.Final'
    implementation 'commons-validator:commons-validator:1.7'
    testImplementation group: 'org.glassfish', name: 'javax.el', version: '3.0.0'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2'
    testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.5.2'
    implementation 'org.springframework:spring-beans:5.3.20'
}

solution suggestion.

Upvotes: 1

Views: 77

Answers (1)

VGR
VGR

Reputation: 44328

You have to make two annotation types, which will be identical except for their import statements. The good news is, they can coexist without any problems:

import java.util.Map;
import java.util.Objects;

import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;

@Target({TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = IsValidSmsLegacy.SmsValidator.class)
public @interface IsValidSmsLegacy {
    // ...
}

And:

import java.util.Map;
import java.util.Objects;

import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import jakarta.validation.Payload;

@Target({TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = IsValidSms.SmsValidator.class)
public @interface IsValidSms {
    // ...
}

And of course, in your code, you’ll have things like:

@IsValidSms
@IsValidSmsLegacy
public class Message {
    // ...
}

Side note: Objects.isNull is not meant to be a universal replacement for the != null check. Objects.isNull is intended for cases where code needs a method reference; for example:

public void process(Collection<String> values) {
    Objects.requireNonNull(values, "Value set cannot be null.");
    if (values.stream().anyMatch(Objects::isNull)) {
        throw new IllegalArgumentException(
            "Value set cannot contain null elements.");
    }

    // ...
}

Upvotes: 1

Related Questions