javaspringspring-bootclassdesign-patterns

How to pass values from application.properties to a java abstract class?


I'm new to Java class design and need help with the following:

Example scenario: I want to pass the company name and email to the BaseEmailMessage class, and fetch these values from the application.properties file.

I also have static variables SIGN_UP_URL and PASSWORD_RESET_URL in the SignUpEmail and PasswordResetEmail classes, respectively, and I need to fetch these from the same properties file.

abstract public class BaseEmailMessage {
    // Need to fetch these from application properties
    // as they are common for all email types
    private String COMPANY_NAME;
    private String COMPANY_EMAIL;
    
    // constructor, methods, getters, and setters...
}

@Component
public class SignUpEmail extends BaseEmailMessage {

    // Need to fetch this from application properties specific to this class only
    private static final String SIGN_UP_URL;
}

@Component
public class PasswordResetEmail extends BaseEmailMessage {

    // Need to fetch this from application properties specific to this class only
    private static final String PASSWORD_RESET_URL;
}

How can I achieve this in a clean and maintainable way? Any best practices to improve the maintainability of this code?

Thanks!

I tried autowiring the Environment instance to the classes, but not sure should I move with that approach or not?


Solution

  • You can use @ConfigurationProperties and constructor injection instead of manually fetching values from the environment. I think it is a maintainable way to achieve what you're asking.

    Avoid using static fields because they're not easily injected by Spring.

    Define your properties clearly in application.properties :

    email.company-name=Example Company
    email.company-email=info@example.com
    email.signup.url=https://example.com/signup
    email.passwordreset.url=https://example.com/reset-password
    

    Define a class to load common email configuration properties:

    @Component
    @ConfigurationProperties(prefix = "email")
    public class EmailProperties {
        private String companyName;
        private String companyEmail;
        
        private final Signup signup = new Signup();
        private final Passwordreset passwordreset = new Passwordreset();
        
        // getters and setters
        
        public static class Signup {
            private String url;
            // getters and setters
        }
        
        public static class Passwordreset {
            private String url;
            // getters and setters
        }
    
        // getters and setters for top-level properties
        public String getCompanyName() { return companyName; }
        public void setCompanyName(String companyName) { this.companyName = companyName; }
    
        public String getCompanyEmail() { return companyEmail; }
        public void setCompanyEmail(String companyEmail) { this.companyEmail = companyEmail; }
    
        public Signup getSignup() { return signup; }
        public Passwordreset getPasswordreset() { return passwordreset; }
    }
    

    Use constructor injection in your abstract class BaseEmailMessage:

    public abstract class BaseEmailMessage {
        protected final String companyName;
        protected final String companyEmail;
    
        protected BaseEmailMessage(String companyName, String companyEmail) {
            this.companyName = companyName;
            this.companyEmail = companyEmail;
        }
    
        // methods, getters, etc.
    }
    

    Subclasses inject their specific URLs in constructors, along with the common properties:

    @Component
    public class SignUpEmail extends BaseEmailMessage {
        private final String signupUrl;
    
        public SignUpEmail(EmailProperties emailProperties) {
            super(emailProperties.getCompanyName(), emailProperties.getCompanyEmail());
            this.signupUrl = emailProperties.getSignup().getUrl();
        }
    
        // methods using signupUrl
        public String getSignupUrl() {
            return signupUrl;
        }
    }
    

    I think this way is maintainable because it is clear separation, easy management and constructor injection.