How to develop a MuleSoft connector

Posted 3 months ago by Filip Vavera

GitLab logo


After a brief introduction to the MuleSoft platform in my first blog, I would like to share my first experience with developing a connector. I decided to develop a Gitlab connector because it is a great system and I did not find a connector that would be developed for it.

First, I’m going to focus on describing the options you have when you decide to develop a connector. Next, I will describe, how to structure the connector and what are the specifics that you need to care of in comparison to the development of a standard Java program.

Development options

You have basically three options when deciding about the way to develop your connector. You can choose to develop the connector based on Java SDK for your app, with SOAP approach or as RESTfull API. You can read more about this in the official documentation.

Gitlab has a Java SDK available, so I have chosen to develop the connector that way. It has the advantage that I do not have to bother with low-level logic (such as how to call a GET request and others) because the Gitlab Java SDK solves this for me. I just call appropriate methods from the SDK.

If there is no Java SDK available for your project, you will have to develop it from scratch. You can automate parts of this task, such as code generation from YAML API specification, but describing it goes beyond the scope of this article.


The structure is briefly described in the MuleSoft documentation so I will do just a quick high-level introduction here.

Most fundamentally the connector consist of two parts. The config class and the connector itself. The config class stores the data related to the connector instance. In my case, it is the username, password, and the Gitlab host URL. There is also the option to enter API key but the connector is able to obtain it automatically based on the username and password too. The config also contains read-only API endpoint pointer which is the gate to all API calls. It is established after connecting to the Gitlab with credentials. All the connection and disconnection logic is located in the config class too.


@ConnectionManagement(friendlyName = "Configuration")
public class ConnectorConfig {
    private String gitlabHost = "";

    private String privateToken;
    @Connect(strategy = ConnectStrategy.SINGLE_INSTANCE)
    public void connect(@ConnectionKey final String username, @Password final String password, @Optional final Boolean ignoreCertificationErrors, @Optional final Integer requestTimeout) throws ConnectionException {
        ... // Connection logic

    public void disconnect() {
        ... // Disconection logic

    public void testConnectivity(@ConnectionKey final String username, @Password final String password, @Optional final Boolean ignoreCertificationErrors, @Optional final Integer requestTimeout) throws ConnectionException {
        ... // Test connectivity logic

    public boolean isConnected() {
        ... // Validation connection logic


All processor methods that call the Gitlab API are included in the connector class. You could implement most of the methods with just one line by calling the appropriate SDK method. I decided to improve the code by adding logging and exception handling. I also decided that processors inputs will be just basic data types (Integer, String, etc.) and enums in some cases. The choice was made to unify the approach (SDK mismatches that it sometimes requires the class, while sometimes just the ID of the element) and simplify the usage of the connector. I often used annotation “FriendlyName” too to make the connector look more consistent.

Here is an example processor:

public GitlabMergeRequest acceptMergeRequest(@FriendlyName("Project ID") final Integer projectId, @FriendlyName("Merge Request ID") final Integer mergeRequestId, @Optional final String mergeCommitMessage) throws IOException {
    final GitlabProject project;
    final GitlabMergeRequest result;

    LOGGER.trace("Trying to load project {}...", projectId);
    try {
        project = this.config.getApiHandler().getProject(projectId);
    catch (final IOException ex) {
        LOGGER.error("Project {} was not loaded.", projectId);
        throw ex;
    if (project == null) {
        LOGGER.error("Project {} was not loaded (return \"null\").", projectId);
        throw new IOException("Project was not loaded (return \"null\").");
    LOGGER.debug("Project {} was loaded correctly.", projectId);

    LOGGER.trace("Trying to accept merge request {}...", mergeRequestId);
    try {
        result = this.config.getApiHandler().acceptMergeRequest(project, mergeRequestId, mergeCommitMessage);
    catch (final IOException ex) {
        LOGGER.error("Accepting merge request {} failed.", mergeRequestId);
        throw ex;
    if (result == null) {
        LOGGER.error("Accepting merge request {} failed (return \"null\").", mergeRequestId);
        throw new IOException("Accepting merge request failed (return \"null\").");
    LOGGER.debug("Merge request {} was accepted correctly.", mergeRequestId);

    return result;

If you would like to see the complete connector, you can find and download it on Github. You are welcome to use it and improve it through fork and pull request. Looking forward to hearing your feedback.

Filip Vavera

Leave a Reply

Related articles

Contact us now

Your message has been sent. Thank you!