Make $elemMatch (projection) return all objects that match criteria

In order to return multiple subdocuments, you're going to need to use the aggregation framework. This will return all of the subdocuments you're looking for:

db.zip.aggregate(
  {$match: {zipcode: 63109}},
  {$unwind: "$students"},
  {$match: {"students.school": 102}}
)

You can do various things to get different output, but this will return:

{
    "result" : [
        {
            "_id" : 1,
            "zipcode" : 63109,
            "students" : {
                "name" : "john",
                "school" : 102,
                "age" : 10
            }
        },
        {
            "_id" : 1,
            "zipcode" : 63109,
            "students" : {
                "name" : "jess",
                "school" : 102,
                "age" : 11
            }
        },
        {
            "_id" : 4,
            "zipcode" : 63109,
            "students" : {
                "name" : "barney",
                "school" : 102,
                "age" : 7
            }
        }
    ],
    "ok" : 1
}

Previous and incorrect answer:

This should work as of today. See https://docs.mongodb.com/v3.2/reference/operator/projection/positional/#array-field-limitations

You should get the correct result when querying using $elemMatch in the query and exposing the sub-document in the projection like following:

db.schools.find( { zipcode: 63109, students: { $elemMatch: { school: 102 } } },
                 { 'students.$': 1 } )


New answer

Limiting the list of sub-documents to those matching the query is as of now not possible using find(). Please take aggregate() instead or take one of the following possibilities:

You could either get all the sub-documents of the matching document by adding the array-property in the projection:

db.schools.find( { zipcode: 63109, students: { $elemMatch: { school: 102 } } }, { 'students': 1 })
> { "_id" : 1, "students" : [ { "name" : "john", "school" : 102, "age" : 10 }, { "name" : "jess", "school" : 102, "age" : 11 }, { "name" : "jeff", "school" : 108, "age" : 15 } ] }
> { "_id" : 4, "students" : [ { "name" : "barney", "school" : 102, "age" : 7 } ] }

Or you can get the first item matching the $elemMatch query on the sub-documents:

db.schools.find( { zipcode: 63109, students: { $elemMatch: { school: 102 } } }, { 'students.$': 1 })
> { "_id" : 1, "students" : [ { "name" : "john", "school" : 102, "age" : 10 } ] }
> { "_id" : 4, "students" : [ { "name" : "barney", "school" : 102, "age" : 7 } ] }