Generating an Apex Class Using the SOAP Tooling API and MetadataContainer

QESTION

It’s fairly easy to create an ApexClass in the SOAP Tooling API:

ConnectorConfig partnerConfig = new ConnectorConfig();
partnerConfig.setManualLogin(true);

PartnerConnection partnerConnection = com.sforce.soap.partner.Connector.newConnection(partnerConfig);
LoginResult lr = partnerConnection.login(<username>, <password> + <security_token>);

ConnectorConfig toolingConfig = new ConnectorConfig();
toolingConfig.setSessionId(lr.getSessionId());
toolingConfig.setServiceEndpoint(lr.getServerUrl().replace('u', 'T'));

toolingConnection = com.sforce.soap.tooling.Connector.newConnection(toolingConfig);

try
{
    String classBody = "public class Messages {\n"
        + "public string SayHello() {\n"
        + " return 'Hello';\n" + "}\n"
        + "}";

    ApexClass apexClass = new ApexClass();
    apexClass.setBody(classBody);
    ApexClass[] classes = {apexClass};
    classId = null;

    SaveResult[] saveResults = toolingConnection.create(classes);
    if (saveResults[0].isSuccess())
    {
        System.out.println("Successfully created Class: " + saveResults[0].getId());
        classId = saveResults[0].getId();
    }
    else
    {
        System.out.println("Error: could not create Class ");
        System.out.println("The error reported was: " + saveResults[0].getErrors()[0].getMessage() + "\n");
        throw new Exception("Error");
    }
}
catch(Exception e)
{
    //Handle exception
}

This immediately creates the class in your org. However, I would like to be able to utilize the MetadataContainer functionality to create classes in this manner:

ConnectorConfig partnerConfig = new ConnectorConfig();
partnerConfig.setManualLogin(true);

PartnerConnection partnerConnection = com.sforce.soap.partner.Connector.newConnection(partnerConfig);
LoginResult lr = partnerConnection.login(<username>, <password> + <security_token>);

ConnectorConfig toolingConfig = new ConnectorConfig();
toolingConfig.setSessionId(lr.getSessionId());
toolingConfig.setServiceEndpoint(lr.getServerUrl().replace('u', 'T'));

toolingConnection = com.sforce.soap.tooling.Connector.newConnection(toolingConfig);

try
{
    MetadataContainer Container = new MetadataContainer();
    Container.setName("SampleContainer2");

    MetadataContainer[] Containers = { Container };
    SaveResult[] containerResults = toolingConnection.create(Containers);
    if (containerResults[0].isSuccess())
    {
        System.out.println("Successfully created MetadataContainer: " + containerResults[0].getId());
        containerId = containerResults[0].getId();
    }
    else
    {
        System.out.println("Error: could not create MetadataContainer ");
        System.out.println("The error reported was: " + containerResults[0].getErrors()[0].getMessage() + "\n");
        throw new Exception("Error");
    }

    String classBody = "public class Messages {\n"
        + "public string SayHello() {\n"
        + " return 'Hello';\n" + "}\n"
        + "}";

    ApexClassMember createClassMember = new ApexClassMember();
    createClassMember.setFullName("Messages");
    createClassMember.setBody(classBody);

    createClassMember.setMetadataContainerId(containerId);
    ApexClassMember[] createClassMembers = { createClassMember };

    SaveResult[] createResults = toolingConnection.create(createClassMembers);
    if (createResults[0].isSuccess())
    {
        System.out.println("Successfully created class in metadata container: " + createResults[0].getId());
    }
    else
    {
        System.out.println("Error: could not create Class Member in metadata container");
        System.out.println("The error reported was: " + createResults[0].getErrors()[0].getMessage() + "\n");
        throw new Exception("Error");
    }
}
catch(Exception e)
{
    //Handle exception
}

When I try this, I get an error as follows:

Error: could not create Class Member in metadata container
The error reported was: Required fields are missing: [Content]

Adding a line to populate the Content field on ApexClassMetadata only results in a different error:

The error reported was: Unable to create/update fields: Content. Please check the security settings of this field and verify that it is read/write for your profile or permission set.

My question, therefore: is it possible to create classes using MetatadataContainers instead of just updating them? Salesforce’s own documentation isn’t clear on this matter, which is why I’ve come here.

ANSWER

I’ve always split the Apex classes based on them being an insert or an update. The inserts I processed via the direct create method. The updates needed to be handled separately via a MetadataContainer and ContainerAsyncRequest.

The trick is that the ApexClassMember has ContentEntityId as a required field. You won’t be able to populate that without first inserting the ApexClass via the create method. You could create an empty class with the correct name first and then use the ApexClassMember with the correct body and Id. Id does mean more API calls to get the same work done. The only advantage I can think of would be handling any dependencies between the classes being updated and created.

There is a similar historic question in How do I use the Tooling API to create a new Apex Trigger?.