Jakarta EE Logo

Jakarta EE explained clearly - build modern enterprise applications with these APIs: Part 1

In this article, we provide an overview of the most important Jakarta EE standards and APIs that are essential for the development of modern Java enterprise applications. Learn how these technologies help to create scalable and maintainable solutions.

Enterprise applications in Java - powerful, modular and better than ever: Jakarta EE provides you with a standard set of tools to cover everything from messaging to security. In this series of articles, you will get to know the most important ones - in a practical way and at a glance.

Clever processing of MIME types - with Jakarta Activation

Jakarta Activation helps you to dynamically process content based on its MIME type - typically for emails or web services. You register suitable processing components, e.g. for text or images.

<dependency>
  <groupId>jakarta.activation</groupId>
  <artifactId>jakarta.activation-api</artifactId>
</dependency>
import jakarta.activation.*;
import java.io.*;

public class MimeProcessor {

    public static void main(String[] args) {
        MailcapCommandMap commandMap = new MailcapCommandMap();
        commandMap.addMailcap("text/plain;; x-java-content-handler=com.example.TextHandler");
        commandMap.addMailcap("image/jpeg;; x-java-content-handler=com.example.ImageHandler");
        CommandMap.setDefaultCommandMap(commandMap);

        DataSource source = new FileDataSource("beispiel.txt");
        DataHandler handler = new DataHandler(source);

        try (InputStream is = handler.getInputStream()) {
            System.out.println("Typ: " + handler.getContentType());
            System.out.println("Inhalt: " + new String(is.readAllBytes()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Less boilerplate thanks to Jakarta Annotations

With Jakarta Annotations, you can replace classes with metadata - for example for resource injection, life cycle control or configuration. This reduces your code enormously.

<dependency>
  <groupId>jakarta.annotation</groupId>
  <artifactId>jakarta.annotation-api</artifactId>
</dependency>
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import javax.sql.DataSource;

public class ResourceBean {

    @Resource(name = "jdbc/MyDataSource")
    private DataSource dataSource;

    @PostConstruct
    public void init() {
        System.out.println("Datenquelle fertig: " + dataSource);
    }
}

Security ex works - with Jakarta Authentication

With Jakarta Authentication you can create your own login mechanisms. The container takes care of the integration - you just take care of the rules.

<dependency>
  <groupId>jakarta.authentication</groupId>
  <artifactId>jakarta.authentication-api</artifactId>
</dependency>
import jakarta.security.enterprise.*;
import jakarta.security.enterprise.authentication.mechanism.http.*;
import jakarta.security.enterprise.identitystore.*;
import jakarta.security.enterprise.credential.*;
import jakarta.inject.Inject;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.*;

@BasicAuthenticationMechanismDefinition(realmName = "example")
@EmbeddedIdentityStoreDefinition({
    @EmbeddedIdentityStoreDefinition.User(name = "user", password = "password", roles = "user"),
    @EmbeddedIdentityStoreDefinition.User(name = "admin", password = "admin", roles = "admin")
})
public class SimpleAuthenticationMechanism implements AuthenticationMechanism {

    @Inject
    private IdentityStoreHandler identityStoreHandler;

    @Override
    public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response, HttpMessageContext context) throws ServletException {
        if (context.isProtected() && !context.isAuthenticationCompleted()) {
            UsernamePasswordCredential credential = new UsernamePasswordCredential(
                request.getParameter("username"),
                request.getParameter("password")
            );

            CredentialValidationResult result = identityStoreHandler.validate(credential);

            if (result.getStatus() == CredentialValidationResult.Status.VALID) {
                return context.notifyContainerAboutLogin(result.getCallerPrincipal(), result.getCallerGroups());
            } else {
                return context.responseUnauthorized();
            }
        }
        return context.doNothing();
    }
}

Role-based access - with Jakarta Authorization

With the Authorization API, you can assign clearly defined roles and protect methods directly in the code - simply and securely.

<dependency>
  <groupId>jakarta.authorization</groupId>
  <artifactId>jakarta.authorization-api</artifactId>
</dependency>
import jakarta.annotation.security.*;

@DeclareRoles({"user", "admin"})
public class SecureResource {

    @RolesAllowed("admin")
    public void adminOnly() {
        System.out.println("Admin-Zugriff erlaubt.");
    }

    @RolesAllowed({"user", "admin"})
    public void forUsers() {
        System.out.println("Zugriff für Benutzer oder Admin.");
    }
}

Process large volumes of data efficiently - with Jakarta Batch

Perfect for recurring jobs such as sending invoices, importing data or regular processes: Jakarta Batch offers you XML-driven processing with full control.

<dependency>
  <groupId>jakarta.batch</groupId>
  <artifactId>jakarta.batch-api</artifactId>
</dependency>
import jakarta.batch.api.AbstractBatchlet;
import jakarta.batch.api.listener.JobListener;
import jakarta.batch.runtime.context.JobContext;
import jakarta.inject.*;

@Named
public class SimpleBatchlet extends AbstractBatchlet {

    @Inject
    private JobContext jobContext;

    @Override
    public String process() {
        String propValue = jobContext.getProperties().getProperty("batchProp");
        System.out.println("Batchlauf mit Property: " + propValue);
        return "COMPLETED";
    }
}

@Named
public class CustomJobListener implements JobListener {

    @Override
    public void beforeJob() {
        System.out.println("Job startet gleich.");
    }

    @Override
    public void afterJob() {
        System.out.println("Job abgeschlossen.");
    }
}
<job id="simpleBatchJob" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0">
    <step id="step1">
        <batchlet ref="SimpleBatchlet">
            <properties>
                <property name="batchProp" value="value1"/>
            </properties>
        </batchlet>
        <listeners>
            <listener ref="CustomJobListener"/>
        </listeners>
    </step>
</job>

CDI: Clean dependencies and life cycle control

With Jakarta CDI you control the lifecycle and injection of beans - ideal for modular, testable architectures.

<dependency>
  <groupId>jakarta.enterprise</groupId>
  <artifactId>jakarta.enterprise.cdi-api</artifactId>
</dependency>
import jakarta.enterprise.context.RequestScoped;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

@RequestScoped
public class MessagePrinter {

    @Inject
    private MessageService messageService;

    public void printMessage() {
        System.out.println(messageService.getMessage());
    }
}

@ApplicationScoped
public class MessageService {
    public String getMessage() {
        return "Hello, CDI!";
    }
}

Concurrency implemented in a controlled manner - with Jakarta Concurrency

Do you want to execute tasks asynchronously - but in a container-friendly and managed way? Jakarta Concurrency makes it possible.

<dependency>
  <groupId>jakarta.enterprise.concurrent</groupId>
  <artifactId>jakarta.enterprise.concurrent-api</artifactId>
</dependency>
import jakarta.annotation.Resource;
import jakarta.enterprise.concurrent.ManagedExecutorService;
import java.util.concurrent.*;

public class ConcurrencyExample {

    @Resource
    private ManagedExecutorService managedExecutorService;

    public void executeTask() throws Exception {
        Future<String> result = managedExecutorService.submit(() -> "Task abgeschlossen!");
        System.out.println(result.get());
    }
}

Manage configuration centrally - with Jakarta Config

Whether environment variables, properties files or cloud - Jakarta Config allows you to import configurations in a standardized and centralized way.

<dependency>
  <groupId>jakarta.config</groupId>
  <artifactId>jakarta.config-api</artifactId>
</dependency>
app.name=JakartaApp
max.users=100
import jakarta.config.Config;
import jakarta.config.ConfigProvider;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class AppConfig {

    private final String appName;
    private final int maxUsers;

    public AppConfig() {
        Config config = ConfigProvider.getConfig();
        this.appName = config.getValue("app.name", String.class);
        this.maxUsers = config.getValue("max.users", Integer.class);
    }

    public void printConfig() {
        System.out.println("App: " + appName);
        System.out.println("Maximale Nutzer: " + maxUsers);
    }
}

Connecting external systems - with Jakarta Connectors

If you need to communicate with an ERP, CRM or other enterprise system, Jakarta Connectors help you via standardized resource adapters.

<dependency>
  <groupId>jakarta.resource</groupId>
  <artifactId>jakarta.resource-api</artifactId>
</dependency>
import jakarta.annotation.Resource;
import jakarta.resource.cci.*;

public class SimpleJcaExample {

    @Resource(name = "eis/SimpleConnectionFactory")
    private ConnectionFactory connectionFactory;

    public void executeSimpleInteraction() {
        try (Connection connection = connectionFactory.getConnection()) {
            System.out.println("EIS-Verbindung erfolgreich.");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Data access made easy - with Jakarta Data

Jakarta Data offers you a repository approach like in Spring: Methods such as findByName automatically generate your queries.

<dependency>
  <groupId>jakarta.data</groupId>
  <artifactId>jakarta.data-api</artifactId>
</dependency>
import jakarta.data.repository.CrudRepository;

public interface ProductRepository extends CrudRepository<Product, Long> {
    List<Product> findByName(String name);
}

Deployment data via API - with Jakarta Deployment

This API is often used by containers to obtain information about deployments - usually relevant for you as a developer in the background.

<dependency>
  <groupId>jakarta.deployment</groupId>
  <artifactId>jakarta.deployment-api</artifactId>
</dependency>

Simple injection without a lifecycle - with Jakarta Inject

Jakarta Inject is used for simple dependency injection without context management - straightforward and efficient.

<dependency>
  <groupId>jakarta.inject</groupId>
  <artifactId>jakarta.inject-api</artifactId>
</dependency>
import jakarta.inject.Inject;
import jakarta.inject.Named;

@Named
public class SimpleService {
    public String getInfo() {
        return "Hello from SimpleService!";
    }
}

public class Consumer {

    @Inject
    private SimpleService simpleService;

    public void displayServiceInfo() {
        System.out.println(simpleService.getInfo());
    }
}

Conclusion: The toolbox for modern Java applications

Whether file handling, data management or security: Jakarta EE provides you with robust building blocks to develop scalable, maintainable and standardized enterprise applications. Step by step, you combine exactly the components you need.1

To be continued ...
In the second part we show you APIs for JSON, messaging, web services and much more.

Further links

Jakarta EE is a project of the Eclipse Foundation. The logo is a registered trademark.

Marius Dienel

About ME

Marius Dienel is an IT specialist in the field of application development and has been working at doubleSlash since 2019. As a software developer, he has expertise in Java EE, Spring and OSGi, among other things. He also deals with DevOps-related topics for the Business Filemanager product.

All contributions from Marius Dienel

Learn more

Further information on our website and in our newsletter

Arrow up